3

Given a string of hex characters, I want to create a byte array from these. E.g. Given the string "1A2B3C", I want my array to contain 0x1A, 0x2B, 0x3C.

I have it working using the code below, but would like to see a more efficient way of doing this.

(Checks for string length etc have been done by this point).

// Go through the string

int k = 0;
stringstream s;
for (int i = 0; i < STRING_SIZE; i++)
{
    // Get 2 digits at a time and store in a temp variable

    s.str("");
    s << key[k++];
    s << key[k++];

    char temp[2];
    memcpy(temp, s.str().c_str(), 2);

    // Get the hex value and store in final array

    actualArray[i] = (unsigned char)strtol(temp, 0, 16);
}
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • 2
    If you have working code and are concerned about efficiency, you might check out https://codereview.stackexchange.com/. – Alex Johnson Aug 09 '18 at 16:18
  • Instead of all that complex and inefficient hassle with a `stringstream` and `memcpy`, you could have done just `const char temp[3] = {key[i], key[i+1]};` (and replace `i++` with `i += 2`). The `3` here makes the string zero-terminated. WIthout that the call of `strtol` has Undefined Behavior. – Cheers and hth. - Alf Aug 09 '18 at 16:37

3 Answers3

4

Assuming key is a std::string the body of your loop could just be:

actualArray[i] = (unsigned char)std::stol(key.substr(i*2, 2), 0, 16);

If key is a char array it would be:

actualArray[i] = (unsigned char)std::stol(std::string(key + i*2, 2), 0, 16);
Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • Brilliant. I had to make a tiny adjustment as strtol doesn't take a string. This works perfectly though. Thanks :) actualArray[i] = (unsigned char)strtol(std::string(key + i*2, 2).c_str(), 0, 16); – user1408542 Aug 09 '18 at 16:34
  • @user1408542 sorry, that should have been `std::stol` – Alan Birtles Aug 09 '18 at 17:20
3

You can use something like this

#include <string>
#include <vector>
#include <cstdint>
#include <cassert>

std::uint8_t char_to_nibble(char c)
{
    assert((c>='0' && c<='9') || (c>='A' && c<='F'));
    if ((c >= '0') && (c <= '9'))
        return c-'0';
    else
        return c-'A' + 0xA;
}

void str2ba(const std::string &src, std::vector<std::uint8_t> &dst)
{
    assert(src.size() % 2 == 0);
    dst.reserve(src.size()/2);
    dst.clear();
    auto it = src.begin();
    while(it != src.end()) {
        std::uint8_t hi = char_to_nibble(*it++);
        std::uint8_t lo = char_to_nibble(*it++);
        dst.push_back(hi*16+lo);
    }
}
user2807083
  • 2,962
  • 4
  • 29
  • 37
  • You should consider handling `'a'..'f'` as well, not just `'A'..'F'`, unless you guarantee that the input is *always* in uppercase before calling `char_to_nibble()`. – Remy Lebeau Aug 09 '18 at 19:51
3

You don't need to go through a stringstream to get the hex values. Map the chars to an int directly and use bitshifting to get the decimal value that hex characters represent.

unsigned int getInt(char c)
{
   if ( isdigit(c) )
   {
      return c-'0';
   }

   if ( c >= 'a' && c <= 'f' )
   {
      return (c - 'a' + 10);
   }

   if ( c >= 'A' && c <= 'F' )
   {
      return (c - 'A' + 10);
   }

   assert(false);

   // Keep the compiler happy.
   return 0;
}

and use it as:

for (int i = 0; i < STRING_SIZE; i += 2)
{
    int hex = getInt(key[i]) << 4 + getInt(key[i+1]);
    // Add hex to the array where you wish to store it.
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270