如何在 C 中将字节数组转换为十六进制字符串?

我有:

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

我想将字节数组转换为字符串,这样我就可以使用 printf 打印字符串:

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

并获得(不需要冒号) :

"00:01:0A:0B"

如果你能帮忙,我将不胜感激。

291807 次浏览
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");

There's no primitive for this in C. I'd probably malloc (or perhaps alloca) a long enough buffer and loop over the input. I've also seen it done with a dynamic string library with semantics (but not syntax!) similar to C++'s ostringstream, which is a plausibly more generic solution but it may not be worth the extra complexity just for a single case.

If you want to store the hex values in a char * string, you can use snprintf. You need to allocate space for all the printed characters, including the leading zeros and colon.

Expanding on Mark's answer:

char str_buf* = malloc(3*X + 1);   // X is the number of bytes to be converted


int i;
for (i = 0; i < x; i++)
{
if (i > 0) snprintf(str_buf, 1, ":");
snprintf(str_buf, 2, "%02X", num_buf[i]);  // need 2 characters for a single hex value
}
snprintf(str_buf, 2, "\n\0"); // dont forget the NULL byte

So now str_buf will contain the hex string.

ZincX's solution adapted to include colon delimiters:

char buf[] = {0,1,10,11};
int i, size = sizeof(buf) / sizeof(char);
char *buf_str = (char*) malloc(3 * size), *buf_ptr = buf_str;
if (buf_str) {
for (i = 0; i < size; i++)
buf_ptr += sprintf(buf_ptr, i < size - 1 ? "%02X:" : "%02X\0", buf[i]);
printf("%s\n", buf_str);
free(buf_str);
}

This is one way of performing the conversion:

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


#define l_word 15
#define u_word 240


char *hex_str[]={"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};


main(int argc,char *argv[]) {




char *str = malloc(50);
char *tmp;
char *tmp2;


int i=0;




while( i < (argc-1)) {
tmp = hex_str[*(argv[i]) & l_word];
tmp2 = hex_str[*(argv[i]) & u_word];


if(i == 0) { memcpy(str,tmp2,1); strcat(str,tmp);}
else { strcat(str,tmp2); strcat(str,tmp);}
i++;
}


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


}

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);
}

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
}

I just wanted to add the following, even if it is slightly off-topic (not standard C), but I find myself looking for it often, and stumbling upon this question among the first search hits. The Linux kernel print function, printk, also has format specifiers for outputting array/memory contents "directly" through a singular format specifier:

https://www.kernel.org/doc/Documentation/printk-formats.txt

Raw buffer as a hex string:
%*ph    00 01 02  ...  3f
%*phC   00:01:02: ... :3f
%*phD   00-01-02- ... -3f
%*phN   000102 ... 3f


For printing a small buffers (up to 64 bytes long) as a hex string with
certain separator. For the larger buffers consider to use
print_hex_dump().

... however, these format specifiers do not seem to exist for the standard, user-space (s)printf.

I'll add the C++ version here for anyone who is interested.

#include <iostream>
#include <iomanip>
inline void print_bytes(char const * buffer, std::size_t count, std::size_t bytes_per_line, std::ostream & out) {
std::ios::fmtflags flags(out.flags()); // Save flags before manipulation.
out << std::hex << std::setfill('0');
out.setf(std::ios::uppercase);
for (std::size_t i = 0; i != count; ++i) {
auto current_byte_number = static_cast<unsigned int>(static_cast<unsigned char>(buffer[i]));
out << std::setw(2) << current_byte_number;
bool is_end_of_line = (bytes_per_line != 0) && ((i + 1 == count) || ((i + 1) % bytes_per_line == 0));
out << (is_end_of_line ? '\n' : ' ');
}
out.flush();
out.flags(flags); // Restore original flags.
}

It will print the hexdump of the buffer of length count to std::ostream out (you can make it default to std::cout). Every line will contain bytes_per_line bytes, each byte is represented using uppercase two digit hex. There will be a space between bytes. And at end of line or end of buffer it will print a newline. If bytes_per_line is set to 0, then it will not print new_line. Try for yourself.

What complex solutions!
Malloc and sprints and casts oh my. (OZ quote)
and not a single rem anywhere. Gosh

How about something like this?

main()
{
// the value
int value = 16;


// create a string array with a '\0' ending ie. 0,0,0
char hex[]= {0,0,'\0'};
char *hex_p=hex;


//a working variable
int TEMP_int=0;


// get me how many 16s are in this code
TEMP_int=value/16;


// load the first character up with
// 48+0 gives you ascii 0, 55+10 gives you ascii A
if (TEMP_int<10) {*hex_p=48+TEMP_int;}
else {*hex_p=55+TEMP_int;}


// move that pointer to the next (less significant byte)<BR>
hex_p++;


// get me the remainder after I have divied by 16
TEMP_int=value%16;


// 48+0 gives you ascii 0, 55+10 gives you ascii A
if (TEMP_int<10) {*hex_p=48+TEMP_int;}
else {*hex_p=55+TEMP_int;}


// print the result
printf("%i , 0x%s",value,hex);


}

For simple usage I made a function that encodes the input string (binary data):

/* Encodes string to hexadecimal string reprsentation
Allocates a new memory for supplied lpszOut that needs to be deleted after use
Fills the supplied lpszOut with hexadecimal representation of the input
*/
void StringToHex(unsigned char *szInput, size_t size_szInput, char **lpszOut)
{
unsigned char *pin = szInput;
const char *hex = "0123456789ABCDEF";
size_t outSize = size_szInput * 2 + 2;
*lpszOut = new char[outSize];
char *pout = *lpszOut;
for (; pin < szInput + size_szInput; pout += 2, pin++)
{
pout[0] = hex[(*pin >> 4) & 0xF];
pout[1] = hex[*pin & 0xF];
}
pout[0] = 0;
}

Usage:

unsigned char input[] = "This is a very long string that I want to encode";
char *szHexEncoded = NULL;
StringToHex(input, strlen((const char *)input), &szHexEncoded);


printf(szHexEncoded);


// The allocated memory needs to be deleted after usage
delete[] szHexEncoded;

Slightly modified Yannith version. It is just I like to have it as a return value

typedef struct {
size_t len;
uint8_t *bytes;
} vdata;


char* vdata_get_hex(const vdata data)
{
char hex_str[]= "0123456789abcdef";


char* out;
out = (char *)malloc(data.len * 2 + 1);
(out)[data.len * 2] = 0;
   

if (!data.len) return NULL;
   

for (size_t i = 0; i < data.len; i++) {
(out)[i * 2 + 0] = hex_str[(data.bytes[i] >> 4) & 0x0F];
(out)[i * 2 + 1] = hex_str[(data.bytes[i]     ) & 0x0F];
}
return out;
}

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);

This function is suitable where user/caller wants hex string to be put in a charactee array/buffer. With hex string in a character buffer, user/caller can use its own macro/function to display or log it to any place it wants (e.g. to a file). This function also allows caller to control number of (hex) bytes to put in each line.

/**
* @fn
* get_hex
*
* @brief
* Converts a char into bunary string
*
* @param[in]
*     buf Value to be converted to hex string
* @param[in]
*     buf_len Length of the buffer
* @param[in]
*     hex_ Pointer to space to put Hex string into
* @param[in]
*     hex_len Length of the hex string space
* @param[in]
*     num_col Number of columns in display hex string
* @param[out]
*     hex_ Contains the hex string
* @return  void
*/
static inline void
get_hex(char *buf, int buf_len, char* hex_, int hex_len, int num_col)
{
int i;
#define ONE_BYTE_HEX_STRING_SIZE   3
unsigned int byte_no = 0;


if (buf_len <= 0) {
if (hex_len > 0) {
hex_[0] = '\0';
}
return;
}


if(hex_len < ONE_BYTE_HEX_STRING_SIZE + 1)
{
return;
}


do {
for (i = 0; ((i < num_col) && (buf_len > 0) && (hex_len > 0)); ++i )
{
snprintf(hex_, hex_len, "%02X ", buf[byte_no++] & 0xff);
hex_ += ONE_BYTE_HEX_STRING_SIZE;
hex_len -=ONE_BYTE_HEX_STRING_SIZE;
buf_len--;
}
if (buf_len > 1)
{
snprintf(hex_, hex_len, "\n");
hex_ += 1;
}
} while ((buf_len) > 0 && (hex_len > 0));


}

Example: Code

#define DATA_HEX_STR_LEN 5000
char      data_hex_str[DATA_HEX_STR_LEN];


get_hex(pkt, pkt_len, data_hex_str, DATA_HEX_STR_LEN, 16);
//      ^^^^^^^^^^^^                                  ^^
//      Input byte array                              Number of (hex) byte
//      to be converted to hex string                 columns in hex string


printf("pkt:\n%s",data_hex_str)

OUTPUT

pkt:
BB 31 32 00 00 00 00 00 FF FF FF FF FF FF DE E5
A8 E2 8E C1 08 06 00 01 08 00 06 04 00 01 DE E5
A8 E2 8E C1 67 1E 5A 02 00 00 00 00 00 00 67 1E
5A 01

Based on Yannuth's answer but simplified.

Here, length of dest[] is implied to be twice of len, and its allocation is managed by the caller.

void create_hex_string_implied(const unsigned char *src, size_t len, unsigned char *dest)
{
static const unsigned char table[] = "0123456789abcdef";


for (; len > 0; --len)
{
unsigned char c = *src++;
*dest++ = table[c >> 4];
*dest++ = table[c & 0x0f];
}
}

I know this question already has an answer but I think my solution could help someone.

So, in my case I had a byte array representing the key and I needed to convert this byte array to char array of hexadecimal values in order to print it out in one line. I extracted my code to a function like this:

char const * keyToStr(uint8_t const *key)
{
uint8_t offset = 0;
static char keyStr[2 * KEY_SIZE + 1];


for (size_t i = 0; i < KEY_SIZE; i++)
{
offset += sprintf(keyStr + offset, "%02X", key[i]);
}
sprintf(keyStr + offset, "%c", '\0');


return keyStr;
}

Now, I can use my function like this:

Serial.print("Public key: ");
Serial.println(keyToStr(m_publicKey));

Serial object is part of Arduino library and m_publicKey is member of my class with the following declaration uint8_t m_publicKey[32].

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.

You can solve with snprintf and malloc.

char c_buff[50];


u8_number_val[] = { 0xbb, 0xcc, 0xdd, 0x0f, 0xef, 0x0f, 0x0e, 0x0d, 0x0c };


char *s_temp = malloc(u8_size * 2 + 1);


for (uint8_t i = 0; i < u8_size; i++)
{
snprintf(s_temp  + i * 2, 3, "%02x", u8_number_val[i]);
}


snprintf(c_buff, strlen(s_temp)+1, "%s", s_temp );


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


free(s);

OUT: bbccdd0fef0f0e0d0c