How do you convert a byte array to a hexadecimal string in C?

278,820

Solution 1

printf("%02X:%02X:%02X:%02X", buf[0], buf[1], buf[2], buf[3]);

For a more generic way:

int i;
for (i = 0; i < x; i++)
{
    if (i > 0) printf(":");
    printf("%02X", buf[i]);
}
printf("\n");

To concatenate to a string, there are a few ways you can do this. I'd probably keep a pointer to the end of the string and use sprintf. You should also keep track of the size of the array to make sure it doesn't get larger than the space allocated:

int i;
char* buf2 = stringbuf;
char* endofbuf = stringbuf + sizeof(stringbuf);
for (i = 0; i < x; i++)
{
    /* i use 5 here since we are going to add at most 
       3 chars, need a space for the end '\n' and need
       a null terminator */
    if (buf2 + 5 < endofbuf)
    {
        if (i > 0)
        {
            buf2 += sprintf(buf2, ":");
        }
        buf2 += sprintf(buf2, "%02X", buf[i]);
    }
}
buf2 += sprintf(buf2, "\n");

Solution 2

For completude, you can also easily do it without calling any heavy library function (no snprintf, no strcat, not even memcpy). It can be useful, say if you are programming some microcontroller or OS kernel where libc is not available.

Nothing really fancy you can find similar code around if you google for it. Really it's not much more complicated than calling snprintf and much faster.

#include <stdio.h>

int main(){
    unsigned char buf[] = {0, 1, 10, 11};
    /* target buffer should be large enough */
    char str[12];

    unsigned char * pin = buf;
    const char * hex = "0123456789ABCDEF";
    char * pout = str;
    int i = 0;
    for(; i < sizeof(buf)-1; ++i){
        *pout++ = hex[(*pin>>4)&0xF];
        *pout++ = hex[(*pin++)&0xF];
        *pout++ = ':';
    }
    *pout++ = hex[(*pin>>4)&0xF];
    *pout++ = hex[(*pin)&0xF];
    *pout = 0;

    printf("%s\n", str);
}

Here is another slightly shorter version. It merely avoid intermediate index variable i and duplicating laste case code (but the terminating character is written two times).

#include <stdio.h>
int main(){
    unsigned char buf[] = {0, 1, 10, 11};
    /* target buffer should be large enough */
    char str[12];

    unsigned char * pin = buf;
    const char * hex = "0123456789ABCDEF";
    char * pout = str;
    for(; pin < buf+sizeof(buf); pout+=3, pin++){
        pout[0] = hex[(*pin>>4) & 0xF];
        pout[1] = hex[ *pin     & 0xF];
        pout[2] = ':';
    }
    pout[-1] = 0;

    printf("%s\n", str);
}

Below is yet another version to answer to a comment saying I used a "trick" to know the size of the input buffer. Actually it's not a trick but a necessary input knowledge (you need to know the size of the data that you are converting). I made this clearer by extracting the conversion code to a separate function. I also added boundary check code for target buffer, which is not really necessary if we know what we are doing.

#include <stdio.h>

void tohex(unsigned char * in, size_t insz, char * out, size_t outsz)
{
    unsigned char * pin = in;
    const char * hex = "0123456789ABCDEF";
    char * pout = out;
    for(; pin < in+insz; pout +=3, pin++){
        pout[0] = hex[(*pin>>4) & 0xF];
        pout[1] = hex[ *pin     & 0xF];
        pout[2] = ':';
        if (pout + 3 - out > outsz){
            /* Better to truncate output string than overflow buffer */
            /* it would be still better to either return a status */
            /* or ensure the target buffer is large enough and it never happen */
            break;
        }
    }
    pout[-1] = 0;
}

int main(){
    enum {insz = 4, outsz = 3*insz};
    unsigned char buf[] = {0, 1, 10, 11};
    char str[outsz];
    tohex(buf, insz, str, outsz);
    printf("%s\n", str);
}

Solution 3

Similar answers already exist above, I added this one to explain how the following line of code works exactly:

ptr += sprintf(ptr, "%02X", buf[i])

It's quiet tricky and not easy to understand, I put the explanation in the comments below:

uint8 buf[] = {0, 1, 10, 11};

/* Allocate twice the number of bytes in the "buf" array because each byte would
 * be converted to two hex characters, also add an extra space for the terminating
 * null byte.
 * [size] is the size of the buf array */
char output[(size * 2) + 1];

/* pointer to the first item (0 index) of the output array */
char *ptr = &output[0];

int i;

for (i = 0; i < size; i++) {
    /* "sprintf" converts each byte in the "buf" array into a 2 hex string
     * characters appended with a null byte, for example 10 => "0A\0".
     *
     * This string would then be added to the output array starting from the
     * position pointed at by "ptr". For example if "ptr" is pointing at the 0
     * index then "0A\0" would be written as output[0] = '0', output[1] = 'A' and
     * output[2] = '\0'.
     *
     * "sprintf" returns the number of chars in its output excluding the null
     * byte, in our case this would be 2. So we move the "ptr" location two
     * steps ahead so that the next hex string would be written at the new
     * location, overriding the null byte from the previous hex string.
     *
     * We don't need to add a terminating null byte because it's been already 
     * added for us from the last hex string. */  
    ptr += sprintf(ptr, "%02X", buf[i]);
}

printf("%s\n", output);

Solution 4

Here is a method that is way way faster :

#include <stdlib.h>
#include <stdio.h>

unsigned char *     bin_to_strhex(const unsigned char *bin, unsigned int binsz,
                                  unsigned char **result)
{
  unsigned char     hex_str[]= "0123456789abcdef";
  unsigned int      i;

  if (!(*result = (unsigned char *)malloc(binsz * 2 + 1)))
    return (NULL);

  (*result)[binsz * 2] = 0;

  if (!binsz)
    return (NULL);

  for (i = 0; i < binsz; i++)
    {
      (*result)[i * 2 + 0] = hex_str[(bin[i] >> 4) & 0x0F];
      (*result)[i * 2 + 1] = hex_str[(bin[i]     ) & 0x0F];
    }
  return (*result);
}

int                 main()
{
  //the calling
  unsigned char     buf[] = {0,1,10,11};
  unsigned char *   result;

  printf("result : %s\n", bin_to_strhex((unsigned char *)buf, sizeof(buf), &result));
  free(result);

  return 0
}

Solution 5

Solution

Function btox converts arbitrary data *bb to an unterminated string *xp of n hexadecimal digits:

void btox(char *xp, const char *bb, int n) 
{
    const char xx[]= "0123456789ABCDEF";
    while (--n >= 0) xp[n] = xx[(bb[n>>1] >> ((1 - (n&1)) << 2)) & 0xF];
}

Example

#include <stdio.h>

typedef unsigned char uint8;

void main(void) 
{
    uint8 buf[] = {0, 1, 10, 11};
    int n = sizeof buf << 1;
    char hexstr[n + 1];

    btox(hexstr, buf, n);
    hexstr[n] = 0; /* Terminate! */
    printf("%s\n", hexstr);
}

Result: 00010A0B.

Live: Tio.run.

Share:
278,820
Steve Walsh
Author by

Steve Walsh

Head of Software, Nixplay website You can see more about me here

Updated on July 08, 2022

Comments

  • Steve Walsh
    Steve Walsh almost 2 years

    I have:

    uint8 buf[] = {0, 1, 10, 11};
    

    I want to convert the byte array to a string such that I can print the string using printf:

    printf("%s\n", str);
    

    and get (the colons aren't necessary):

    "00:01:0A:0B"
    

    Any help would be greatly appreciated.

    • whatacold
      whatacold about 7 years
      buf[i] must be casted to unsigned char, or it will overflow if buf[i] > 127, that is:buf_ptr += sprintf(buf_ptr, "%02X", (unsigned char)buf[i]);
  • Steve Walsh
    Steve Walsh almost 13 years
    Thank you Mark - my problem is a bit more complicated. I actually have a buffer with a length of X bytes. I was hoping to find a generic way of doing this for X bytes and having a string as the result.
  • Mark Synowiec
    Mark Synowiec almost 13 years
    Just updated to add code for handling any given number of bytes... assuming x is the length.
  • Steve Walsh
    Steve Walsh almost 13 years
    Thank you again Mark, but the thing I was finding most tricky for this problem is how to print this to a string.
  • Mr. Shickadance
    Mr. Shickadance almost 13 years
    This answer should still be sufficient then. Do you mean that you want to store a string with the hex values? If so checkout snprintf instead of printf. It allows you to print to a char * string.
  • Mark Synowiec
    Mark Synowiec almost 13 years
    yeah, that would be a little more difficult. i updated the post to use sprintf, using an extra pointer to keep track of where i want to write to (ie the end of the string).
  • Mr. Shickadance
    Mr. Shickadance almost 13 years
    @Mark I just submitted an answer, but since you were first I'll only point out that you may want to tack on a \0 byte at the end there.
  • easytiger
    easytiger almost 11 years
    printf("%02X", (unsigned char)buf[i]); should be used as the original will cause overflow for unsigned chars
  • niemiro
    niemiro almost 10 years
    This code contains a bug which manifests itself only on strange non-printable inputs (haven't had time to dig into exactly what's going on mathematically). Try to encode the binary of hexadecimal ca9e3c972f1c5db40c0b4a66ab5bc1a20ca4457bdbe5e0f8925896d5ed37‌​d726 and you'll get ÌaÌe3cÌ72f1c5dÌ40c0b4a66Ìb5bÌ1Ì20cÌ4457bÌbÌ5Ì0Ì8Ì258Ì6Ì5Ìd37‌​Ì726 out. To fix this, the bit inside hex_str in the first line of the for loop needs to be changed to (input[i] >> 4) & 0x0F as in @kriss's answer. Then it works fine.
  • xordon
    xordon almost 10 years
    this overwrites the first 2 characters over and over.. right?
  • kriss
    kriss over 9 years
    It's not a trick, merely a constant. In the context of the question it is clear that the length of the source we want to convert to hexadecimal is well known (I could have put some hardcoded 4 instead of sizeof). In the general case the function should be called on some input of known length and the target buffer have 3 times + 1 bytes available. This must be ensured by the caller, there is no reason for the conversion function to perform that task. Calling strlen() may be a way to find the source size in some cases, but not always. What if the number to convert to hex contains zeroes ?
  • Jason Pepas
    Jason Pepas over 8 years
    Inspired by your function, I wrote a version which also returns the number of bytes written to the output buffer, similar to snprintf, etc. gist.github.com/cellularmitosis/0d8c0abf7f8aa6a2dff3
  • kriss
    kriss over 8 years
    OK, now you have two hex digits. It remains to add separators and take care of the other bytes to converts. Maybe with a loop ? Make it a function and you'll have something similar to mine (but rather verbose and hard to read). Maybe you should at least finish the job before calling names on other posters?
  • kriss
    kriss over 8 years
    And a word about comments in source code (not REM, that's BASIC keyword for comments, plese avoid that): comments saying in english what the code is doing is very very bad practice! Yes programmers are supposed to know what modulo operators mean (give the remains) and that division count the number of times a number appears in another one... and that printf print the result. Oh my!
  • Hintron
    Hintron about 7 years
    Why not printf("%02hhX", buf[i])?
  • Cecil Ward
    Cecil Ward almost 7 years
    I think you should make your output buffer the correct size automatically using char str[ sizeof(buf)*3 + 1 ];
  • Cecil Ward
    Cecil Ward almost 7 years
    Also a lot more consts would safeguard you. Eg "const unsigned char const * p" so that you can make sure that input buffers are not written to. One makes the address (or 'pointer') constant, or variable, and the other makes the memory at that address read-only or not. Often stops you from getting the pointers mixed up. And also, having meaningful names which document which buffers and pointers are for input and output would help too.
  • Cecil Ward
    Cecil Ward almost 7 years
    Bug - doesn't check for the malloc() failure.
  • Cecil Ward
    Cecil Ward almost 7 years
    It is always better to use unsigned char absolutely everywhere as nobody wants the risk of signed chars (a mad DEC PDP11 hardware feature), and that way you don't run the risk of signed comparisons going wrong or signed right shifts corrupting values. In this case, to be fair, the code does defensively do an & 0x0F everywhere which protects you here.
  • Cecil Ward
    Cecil Ward almost 7 years
    The bin input parameter ought to be const unsigned char const * bin, to declare the memory as read-only for the purposes of this routine.
  • Cecil Ward
    Cecil Ward almost 7 years
    I would much prefer to return the address of the result buffer as the function return value - it would be much more efficient, for numerous reasons, and easier to read.
  • kriss
    kriss almost 7 years
    @Cecil War: unless my code is bogus using const won't safeguard much except as you say mixing up pointers or using the same pointer for input and output (ok, still possible). But it will also help compiler to optimize code. Even better would be to also use restrict keyword (too bad C99 not C++, but often exists as a compiler extension). What do you want more meaninfull as to call input buffer in and output buffer out ? I could also opt for using a string and returning a copy instead of providing output buffer, in modern C++ optimizers are good enough to don't care much.
  • Cecil Ward
    Cecil Ward almost 7 years
    The advantage of my explicit sizeof expression above is that it protects you by resizing the output buffer automatically if you add one more value into the initialiser list. In your updated version, you have to hope the constants are correct/maintained. An assert would be another route, more flexible in some situations.
  • Cecil Ward
    Cecil Ward almost 7 years
    The thing with truncating the buffer is that the user would end up with half a data structure missing in some cases, so might read on past the end of the output into garbage land. It also makes all kinds of odd assumptions about the presence or absence of zero-terminators and corrupts the output by writing a zero over the last byte (even if the user does not want one). The best thing would be to assume nothing and not have any zero-terminators assumed or added. That's for the caller.
  • kriss
    kriss almost 7 years
    @Cecil Ward: depends on the kind of project you are working on. In some context all memory allocation may be forbidden, in such cases resizing is a no go. I'm not sure what you suggest with your sizeof. Either it has to be be inside caller code besause in callee it just won't work (because of pointer decay), or the input parameters could be defined as reference to arrays in which case the size can even be implicit. But in non trivial cases the same function could be used for instance to append an hex value to some text message then the actual memory checks become different.
  • kriss
    kriss almost 7 years
    @Cecil Ward: doing nothing is indeed an option. That's what I wrote in my code comment.
  • Yannuth
    Yannuth over 6 years
    I've integrated the propositions of Cecil Ward, thanks for feedback
  • Mark Terrill
    Mark Terrill over 4 years
    Brilliant logic. Was looking for an hour for an elegant non C++ string answer to this challenge!