Nirav Trivedi
Nirav Trivedi

Reputation: 79

Converting from char string to an array of uint8_t?

I'm reading a string from a file so it's in the form of a char array. I need to tokenize the string and save each char array token as a uint8_t hex value in an array.

char* starting = "001122AABBCC";
// ...
uint8_t[] ending = {0x00,0x11,0x22,0xAA,0xBB,0xCC}

How can I convert from starting to ending? Thanks.

Upvotes: 7

Views: 7421

Answers (9)

winux
winux

Reputation: 482

This simple solution should work for your problem

char* starting = "001122AABBCC";
uint8_t ending[12];

// This algo will work for any size of starting
// However, you have to make sure that the ending have enough space.

int i=0;
while (i<strlen(starting))
{
     // convert the character to string
     char str[2] = "\0";
     str[0] = starting[i];

     // convert string to int base 16
     ending[i]= (uint8_t)atoi(str,16);

     i++;
}

Upvotes: 0

Niall
Niall

Reputation: 30624

I think any canonical answer (w.r.t. the bounty notes) would involve some distinct phases in the solution:

  • Error checking for valid input
    • Length check and
    • Data content check
  • Element conversion
  • Output creation

Given the usefulness of such conversions, the solution should probably include some flexibility w.r.t. the types being used and the locale required.

From the outset, given the date of the request for a "more canonical answer" (circa August 2014) liberal use of C++11 will be applied.

An annotated version of the code, with types corresponding to the OP:

std::vector<std::uint8_t> convert(std::string const& src)
{
  // error check on the length
  if ((src.length() % 2) != 0) {
    throw std::invalid_argument("conversion error: input is not even length");
  }

  auto ishex = [] (decltype(*src.begin()) c) {
    return std::isxdigit(c, std::locale()); };

  // error check on the data contents
  if (!std::all_of(std::begin(src), std::end(src), ishex)) {
    throw std::invalid_argument("conversion error: input values are not not all xdigits");
  }

  // allocate the result, initialised to 0 and size it to the correct length
  std::vector<std::uint8_t> result(src.length() / 2, 0);

  // run the actual conversion    
  auto str = src.begin(); // track the location in the string
  std::for_each(result.begin(), result.end(), [&str](decltype(*result.begin())& element) {
    element = static_cast<std::uint8_t>(std::stoul(std::string(str, str + 2), nullptr, 16));
    std::advance(str, 2); // next two elements
  });

  return result;
}

The template version of the code adds flexibility;

template <typename Int /*= std::uint8_t*/,
  typename Char = char,
  typename Traits = std::char_traits<Char>,
  typename Allocate = std::allocator<Char>,
  typename Locale = std::locale>
std::vector<Int> basic_convert(std::basic_string<Char, Traits, Allocate> const& src, Locale locale = Locale())
{
  using string_type = std::basic_string<Char, Traits, Allocate>;

  auto ishex = [&locale] (decltype(*src.begin()) c) {
      return std::isxdigit(c, locale); };

  if ((src.length() % 2) != 0) {
    throw std::invalid_argument("conversion error: input is not even length");
  }

  if (!std::all_of(std::begin(src), std::end(src), ishex)) {
    throw std::invalid_argument("conversion error: input values are not not all xdigits");
  }

  std::vector<Int> result(src.length() / 2, 0);

  auto str = std::begin(src);
  std::for_each(std::begin(result), std::end(result), [&str](decltype(*std::begin(result))& element) {
    element = static_cast<Int>(std::stoul(string_type(str, str + 2), nullptr, 16));
    std::advance(str, 2);
  });

  return result;
}

The convert() function can then be based on the basic_convert() as follows:

std::vector<std::uint8_t> convert(std::string const& src)
{
  return basic_convert<std::uint8_t>(src, std::locale());
}

Live sample.

Upvotes: 1

game development germ
game development germ

Reputation: 1334

You may add your own conversion from set of char { '0','1',...'E','F' } to uint8_t:

uint8_t ctoa(char c)
{
    if( c >= '0' && c <= '9' ) return  c - '0'; 
    else if( c >= 'a' && c <= 'f' ) return 0xA + c - 'a';
    else if( c >= 'A' && c <= 'F' ) return 0xA + c - 'A';
    else return 0;
}

Then it will be easy to convert a string in to array:

uint32_t endingSize = strlen(starting)/2;
uint8_t* ending = new uint8_t[endingSize];

for( uint32_t i=0; i<endingSize; i++ )
{
    ending[i] = ( ctoa( starting[i*2] ) << 4 ) + ctoa( starting[i*2+1] );
}

Upvotes: 0

Seagull
Seagull

Reputation: 3610

uint8_t* ending = static_cast<uint8_t*>(starting);

Upvotes: -1

Jarod42
Jarod42

Reputation: 218323

With C++11, you may use std::stoi for that :

std::vector<uint8_t> convert(const std::string& s)
{
    if (s.size() % 2 != 0) {
        throw std::runtime_error("Bad size argument");
    }
    std::vector<uint8_t> res;
    res.reserve(s.size() / 2);
    for (std::size_t i = 0, size = s.size(); i != size; i += 2) {
        std::size_t pos = 0;
        res.push_back(std::stoi(s.substr(i, 2), &pos, 16));
        if (pos != 2) {
            throw std::runtime_error("bad character in argument");
        }
    }
    return res;
}

Live example.

Upvotes: 1

OfNothing
OfNothing

Reputation: 600

Here is a complete working program. It is based on Rob I's solution, but fixes several problems has been tested to work.

#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>


const char* starting = "001122AABBCC";
int main() 
{
    std::string starting_str = starting;
    std::vector<unsigned char> ending;
    ending.reserve( starting_str.size());
    for (int i = 0 ; i < starting_str.length() ; i+=2) {
        std::string pair = starting_str.substr( i, 2 );
        ending.push_back(::strtol( pair.c_str(), 0, 16 ));
    }

    for(int i=0; i<ending.size(); ++i) {
        printf("0x%X\n", ending[i]);
    }

}

Upvotes: 2

Martin Beckett
Martin Beckett

Reputation: 96177

strtoul will convert text in any base you choose into bytes. You have to do a little work to chop the input string into individual digits, or you can convert 32 or 64bits at a time.

ps uint8_t[] ending = {0x00,0x11,0x22,0xAA,0xBB,0xCC}

Doesn't mean anything, you aren't storing the data in a uint8 as 'hex', you are storing bytes, it's upto how you (or your debugger) interpretes the binary data

Upvotes: 1

Rob I
Rob I

Reputation: 5747

I'd try something like this:

std::string starting_str = starting;
uint8_t[] ending = new uint8_t[starting_str.length()/2];
for (int i = 0 ; i < starting_str.length() ; i+=2) {
    std::string pair = starting_str.substr( i, i+2 );
    ending[i/2] = ::strtol( pair.c_str(), 0, 16 );
}

Didn't test it but it looks good to me...

Upvotes: 0

pinerd314159
pinerd314159

Reputation: 544

uint8_t is typically no more than a typedef of an unsigned char. If you're reading characters from a file, you should be able to read them into an unsigned char array just as easily as a signed char array, and an unsigned char array is a uint8_t array.

Upvotes: 0

Related Questions