C++ convert string to hexadecimal and vice versa

238,185

Solution 1

A string like "Hello World" to hex format: 48656C6C6F20576F726C64.

Ah, here you go:

#include <string>

std::string string_to_hex(const std::string& input)
{
    static const char hex_digits[] = "0123456789ABCDEF";

    std::string output;
    output.reserve(input.length() * 2);
    for (unsigned char c : input)
    {
        output.push_back(hex_digits[c >> 4]);
        output.push_back(hex_digits[c & 15]);
    }
    return output;
}

#include <stdexcept>

int hex_value(unsigned char hex_digit)
{
    static const signed char hex_values[256] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    };
    int value = hex_values[hex_digit];
    if (value == -1) throw std::invalid_argument("invalid hex digit");
    return value;
}

std::string hex_to_string(const std::string& input)
{
    const auto len = input.length();
    if (len & 1) throw std::invalid_argument("odd length");

    std::string output;
    output.reserve(len / 2);
    for (auto it = input.begin(); it != input.end(); )
    {
        int hi = hex_value(*it++);
        int lo = hex_value(*it++);
        output.push_back(hi << 4 | lo);
    }
    return output;
}

(This assumes that a char has 8 bits, so it's not very portable, but you can take it from here.)

Solution 2

string ToHex(const string& s, bool upper_case /* = true */)
{
    ostringstream ret;

    for (string::size_type i = 0; i < s.length(); ++i)
        ret << std::hex << std::setfill('0') << std::setw(2) << (upper_case ? std::uppercase : std::nouppercase) << (int)s[i];

    return ret.str();
}

int FromHex(const string &s) { return strtoul(s.c_str(), NULL, 16); }

Solution 3

Using lookup tables and the like works, but is just overkill, here are some very simple ways of taking a string to hex and hex back to a string:

#include <stdexcept>
#include <sstream>
#include <iomanip>
#include <string>
#include <cstdint>

std::string string_to_hex(const std::string& in) {
    std::stringstream ss;

    ss << std::hex << std::setfill('0');
    for (size_t i = 0; in.length() > i; ++i) {
        ss << std::setw(2) << static_cast<unsigned int>(static_cast<unsigned char>(in[i]));
    }

    return ss.str(); 
}

std::string hex_to_string(const std::string& in) {
    std::string output;

    if ((in.length() % 2) != 0) {
        throw std::runtime_error("String is not valid length ...");
    }

    size_t cnt = in.length() / 2;

    for (size_t i = 0; cnt > i; ++i) {
        uint32_t s = 0;
        std::stringstream ss;
        ss << std::hex << in.substr(i * 2, 2);
        ss >> s;

        output.push_back(static_cast<unsigned char>(s));
    }

    return output;
}

Solution 4

I think there is a much simpler and more elegant solution. Some of the above-mentioned methods may even throw unhandled exceptions in some cases. Here is a fool-proof (as in never goes wrong) and very fast code. Just try it and compare the results in terms of speed and compactness:

#include <string>

// Convert string of chars to its representative string of hex numbers
void stream2hex(const std::string str, std::string& hexstr, bool capital = false)
{
    hexstr.resize(str.size() * 2);
    const size_t a = capital ? 'A' - 1 : 'a' - 1;

    for (size_t i = 0, c = str[0] & 0xFF; i < hexstr.size(); c = str[i / 2] & 0xFF)
    {
        hexstr[i++] = c > 0x9F ? (c / 16 - 9) | a : c / 16 | '0';
        hexstr[i++] = (c & 0xF) > 9 ? (c % 16 - 9) | a : c % 16 | '0';
    }
}

// Convert string of hex numbers to its equivalent char-stream
void hex2stream(const std::string hexstr, std::string& str)
{
    str.resize((hexstr.size() + 1) / 2);

    for (size_t i = 0, j = 0; i < str.size(); i++, j++)
    {
        str[i] = (hexstr[j] & '@' ? hexstr[j] + 9 : hexstr[j]) << 4, j++;
        str[i] |= (hexstr[j] & '@' ? hexstr[j] + 9 : hexstr[j]) & 0xF;
    }
}

Test the code:

#include <iostream>
int main()
{
    std::string s = "Hello World!";
    std::cout << "original string: " << s << '\n';
    stream2hex(s, s);
    std::cout << "hex format: " << s << '\n';
    hex2stream(s, s);
    std::cout << "original one: " << s << '\n';
}

and the result is:

original string: Hello World!
hex format: 48656C6C6F20576F726C6421
original one: Hello World!

Solution 5

As of C++17 there's also std::from_chars. The following function takes a string of hex characters and returns a vector of T:

#include <charconv>

template<typename T>
std::vector<T> hexstr_to_vec(const std::string& str, unsigned char chars_per_num = 2)
{
  std::vector<T> out(str.size() / chars_per_num, 0);

  T value;
  for (std::size_t i = 0; i < str.size() / chars_per_num; i++) {
    std::from_chars<T>(
      str.data() + (i * chars_per_num),
      str.data() + (i * chars_per_num) + chars_per_num,
      value,
      16
    );
    out[i] = value;
  }

  return out;
}
Share:
238,185
Sebtm
Author by

Sebtm

Updated on May 19, 2021

Comments

  • Sebtm
    Sebtm about 3 years

    What is the best way to convert a string to hex and vice versa in C++?

    Example:

    • A string like "Hello World" to hex format: 48656C6C6F20576F726C64
    • And from hex 48656C6C6F20576F726C64 to string: "Hello World"
  • Billy ONeal
    Billy ONeal almost 14 years
    +1, but I would implement the second in terms of istringstream -- strtoul is not a standard library function.
  • Sebtm
    Sebtm almost 14 years
    Why FromHex returns as an int? Should return as a string.
  • Krevan
    Krevan almost 14 years
    @Sebtm: if you call FromHex("10"); it will return 16, since 10 in hex is 16
  • liwp
    liwp over 12 years
    I had to mask the shifted lut index, i.e. (c >> 4) & 0x0F to make this work for me.
  • Timmmm
    Timmmm over 11 years
    This is pretty nice. How would you do it the other way?
  • BatchyX
    BatchyX over 11 years
    This does not work with small character values (e.g. If your string is \x01\x02\x03)
  • Mahmut EFE
    Mahmut EFE over 11 years
    it works, change your string like "\\x01\\x02\\x03". because compiler doesnt compile "\x" character.
  • Colorless Photon
    Colorless Photon almost 11 years
    @Abyx But isn't C++ a superset of C? Doesn't this mean that we can use some stuff from C?
  • richvdh
    richvdh almost 11 years
    It seems to work for small character values, but not large ones. test="\xf0" should encode to "f0", but it gives "fffffff0".
  • richvdh
    richvdh almost 11 years
    I take that back, it does fail on small character values too. std::setw() only has an effect for the next write.
  • richvdh
    richvdh almost 11 years
    Did you actually try running this? it's nowhere near correct.
  • atoMerz
    atoMerz over 10 years
    toHex function gave me strange results. It turns out you should convert the byte into an unsigned 8-bit integer (UINT8 in windows).
  • thomthom
    thomthom over 8 years
    VS2013 complained about uint32_t - had to add <cstdint>.
  • thomthom
    thomthom over 8 years
    I found that << std::setw(2) got reset after each read - and I had to use it inside the for loop. I looked up the docs and they also states that the width is reset in many scenarios: en.cppreference.com/w/cpp/io/manip/setw
  • X-Istence
    X-Istence over 8 years
    Interesting, I never ran into that issue while using the function, but looking at the docs you are correct. Thanks for the fix :-)
  • 43.52.4D.
    43.52.4D. about 8 years
    Does not work with string that contain binary such as 0
  • 43.52.4D.
    43.52.4D. about 8 years
    @Erik Hvatum Does this convert an std::string to hex string? I don't understand what you mean by "Binary data sequence". Could you post an example of calling this function?
  • polfosol ఠ_ఠ
    polfosol ఠ_ఠ about 8 years
    @43.52.4D. It works perfectly. See The live example
  • 43.52.4D.
    43.52.4D. about 8 years
    No, I meant actual binary. When I tried this algorithm on a file, some characters did not get converted to HEX properly.
  • Erik Hvatum
    Erik Hvatum about 8 years
    @43.52.4D The dataToHexString function takes 3 arguments. The first is a pointer to the first byte of memory you wish to have rendered in hex, the second is a pointer to the byte of memory after the last you wish to have as text. The third is a string which is modified to contain the text. For example: vector<uint8_t> a; a.push_back(1); a.push_back(255); string s; dataToHex(a.data(), a.data()+a.size(), s); cout << s << '\n'; // "00ff"
  • polfosol ఠ_ఠ
    polfosol ఠ_ఠ about 8 years
    Did you read the file in binary mode? Can you show me your code or paste it somewhere? @43.52.4D.
  • polfosol ఠ_ఠ
    polfosol ఠ_ఠ about 8 years
  • 43.52.4D.
    43.52.4D. about 8 years
    Hmmm idk, personally when I tried it, it didn't seem to convert all bytes properly. Does it work when the data contains NULL characters?
  • polfosol ఠ_ఠ
    polfosol ఠ_ఠ about 8 years
    Are you serious?? In the second example the string had 8 NULL characters. @43.52.4D.
  • 43.52.4D.
    43.52.4D. about 8 years
    Okay, I'm glad it works fine :) Maybe I had a bug in my code.
  • slawekwin
    slawekwin almost 8 years
    instead of platform specific UINT8 you can cast (int)(unsigned char)
  • yau
    yau over 7 years
    @liwp: you need that mask even after correctly casting to unsigned char?
  • Leśny Rumcajs
    Leśny Rumcajs over 5 years
    I like to browse through C / C++98 answers to finally get to modern C++ answer. :)
  • thomthom
    thomthom over 5 years
    @richvdh - did you find out why fffffff0 would be returned? EDIT: Found the solution below: stackoverflow.com/a/16125797/486990 the double static cast was needed.
  • allo
    allo almost 5 years
    Does it handle '\0' correctly? I seem to get too wrong results when the string contains null bytes.
  • Sean
    Sean over 4 years
    Can someone explain why do we have to do in the other complicated ways? I felt this solution is enough.
  • theicfire
    theicfire over 4 years
    This is a lot simpler because it's printed inside of the function. The other answers are more complicated because they return a solution, and don't print anything.
  • muxator
    muxator over 2 years
    This function correctly handles binary data, but in order to feed that data, you'll have to use the two-parameters version of std::string constructor (see en.cppreference.com/w/cpp/string/basic_string/basic_string): std::string(const char* s, size_type count); Otherwise your test string will be truncated at the first null byte: string_to_hex(std::string("\x00\x01", 2)) == "0001" // correct, versus string_to_hex("\x00\x01") == "" // you are really feeding an empty string to the function
  • IInspectable
    IInspectable about 2 years
    @sea You'll find that part of the added complexity is due to correctness requirements. This solution glosses over this requirement. You'll find out once your input is, say, "Hello\nWorld". This produces a string of hexadecimal digits that can no longer be unambiguously converted back. Indeed, for every problem there is a solution that's simple, intuitive, and wrong.