Edit - Updated to ready bytes not characters
Rather than using .substr()
and calling C strtol
and casting to uint8_t
, you can simply use an istringstream
along with std::setbase(16)
to read the bytes as unsigned
values directly into your vector<uint8_t> msg
. See std::setbase.
For instance you can create an istringstream
from your string containing the hex characters, and then along with your vector of uint8_t
and a temporary unsigned
to read directly into before pushing back into your vector you could do, e.g.
std::string result ("0123456789abcdef"); /* input hex string */
std::string s2; /* string for 2-chars */
std::istringstream ss (result); /* stringstream of result */
std::vector<uint8_t> msg; /* vector of uint8_t */
while ((ss >> std::setw(2) >> s2)) { /* read 2-char at a time */
unsigned u; /* tmp unsigned value */
std::istringstream ss2 (s2); /* create 2-char stringstream */
ss2 >> std::setbase(16) >> u; /* convert hex to unsigned */
msg.push_back((uint8_t)u); /* add value as uint8_t */
}
In that way, each 2 characters in result
read using std::setw(2)
are used to create a 2-character stringstream that is then converted a an unsigned
value using std::setbase(16)
. A complete example would be:
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
int main (void) {
std::string result ("0123456789abcdef"); /* input hex string */
std::string s2; /* string for 2-chars */
std::istringstream ss (result); /* stringstream of result */
std::vector<uint8_t> msg; /* vector of uint8_t */
while ((ss >> std::setw(2) >> s2)) { /* read 2-char at a time */
unsigned u; /* tmp unsigned value */
std::istringstream ss2 (s2); /* create 2-char stringstream */
ss2 >> std::setbase(16) >> u; /* convert hex to unsigned */
msg.push_back((uint8_t)u); /* add value as uint8_t */
}
std::cout << "string: " << result << "\nmsg: \n";
for (auto& h : msg) /* for each element of msg, output hex value */
std::cout << "\t" << std::setfill('0') << std::hex << std::setw(2)
<< (uint32_t)h << '\n';;
}
(note the cast required in the output to explicitly tell cout
to treat the uint8_t
value as an unsigned
value rather than a uint8_t
value which defaults to an character type by default.
Example Use/Output
$ ./bin/hexstr2uint8_t
string: 0123456789abcdef
msg:
01
23
45
67
89
ab
cd
ef
(note there are 8 uint8_t
("byte") values stored this time instead of 16 character values)
It's just an alternative using the C++ iostream features which avoids the need to cast things around rather than calling strtol
directly (which in your case should probably be strtoul
to begin with).
Manual Hex Conversion
In your last comment you indicate that using iostream and stringstream for the conversion is slow. You can attempt to optimize a bit by eliminating the stringstream and using a string::iterator
to step through the string manually converting each character and forming each uint8_t
byte as you go (protecting against a final nibble or 1/2-byte), e.g.
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
/* simple manual conversion of hexchar to value */
uint8_t c2hex (const char c)
{
uint8_t u = 0;
if ('0' <= c && c <= '9')
u = c - '0';
else if ('a' <= c && c <= 'f')
u = c - 'W';
else if ('A' <= c && c <= 'F')
u = c - '7';
else
std::cerr << "error: invalid hex char '" << c << "'\n";
return u;
}
int main (void) {
std::string s ("0123456789abcdef");
std::vector<uint8_t> msg;
for (std::string::iterator n = s.begin(); n != s.end(); n += 2) {
uint8_t u = c2hex (*n); /* save high-nibble */
if (n + 1 != s.end()) /* if low-nibble available */
u = (u << 4) | c2hex (n[1]); /* shift high left 4 & or */
msg.push_back(u); /* store byte in msg */
}
std::cout << "string: " << s << "\nmsg:\n";
for (auto& h : msg)
std::cout << "\t" << std::setfill('0') << std::hex
<< std::setw(2) << (unsigned)h << '\n';
}
(output is the same as above)
If you can guarantee there will always be an even number of characters in your string (bytes only and no 1/2-byte as the final-odd character), you can further optimize by removing the conditional and simply using:
uint8_t u = c2hex (n[1]) | (c2hex (*n) << 4);
Make sure you are compiling with full optimization, e.g. -O3
(or -Ofast
gcc version >= 4.6) on gcc/clang and /Ox
with VS.
Give that a try and compare performance, you can additionally dump the differing versions to assembly and see if there are any additional hints there.