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

Posted

技术标签:

【中文标题】如何在 C 中将字节数组转换为十六进制字符串?【英文标题】:How do you convert a byte array to a hexadecimal string in C? 【发布时间】:2011-09-15 11:14:26 【问题描述】:

我有:

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

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

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

并获取(不需要冒号):

"00:01:0A:0B"

任何帮助将不胜感激。

【问题讨论】:

buf[i]必须强制转换为unsigned char,否则buf[i] > 127会溢出,即:buf_ptr += sprintf(buf_ptr, "%02X", (unsigned char)buf[i]); 【参考方案1】:
printf("%02X:%02X:%02X:%02X", buf[0], buf[1], buf[2], buf[3]);

对于更通用的方式:

int i;
for (i = 0; i < x; i++)

    if (i > 0) printf(":");
    printf("%02X", buf[i]);

printf("\n");

要连接到一个字符串,有几种方法可以做到这一点。我可能会保留一个指向字符串末尾的指针并使用 sprintf。您还应该跟踪数组的大小,以确保它不会大于分配的空间:

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

【讨论】:

谢谢你,马克 - 我的问题有点复杂。我实际上有一个长度为 X 字节的缓冲区。我希望找到一种通用的方法来处理 X 字节并得到一个字符串作为结果。 刚刚更新以添加用于处理任何给定字节数的代码...假设 x 是长度。 再次感谢马克,但我发现这个问题最棘手的是如何将其打印到字符串中。 printf("%02X", (unsigned char)buf[i]); 应该使用原版会导致无符号字符溢出 为什么不printf("%02hhX", buf[i])【参考方案2】:

在 C 中没有用于此的原语。我可能会 malloc(或者可能是 alloca)足够长的缓冲区并循环输入。我还看到它使用具有类似于 C++ 的ostringstream 的语义(但不是语法!)的动态字符串库来完成,这是一个看似更通用的解决方案,但仅针对单个案例可能不值得额外的复杂性。

【讨论】:

【参考方案3】:

如果要将十六进制值存储在char * 字符串中,可以使用snprintf。您需要为所有打印的字符分配空间,包括前导零和冒号。

扩展马克的回答:

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

所以现在str_buf 将包含十六进制字符串。

【讨论】:

这会一遍又一遍地覆盖前 2 个字符。对吗?【参考方案4】:

ZincX 的解决方案适用于包含冒号分隔符:

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

【讨论】:

【参考方案5】:

这是执行转换的一种方式:

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


【讨论】:

【参考方案6】:

为了完成,你也可以很容易地做到这一点,而无需调用任何繁重的库函数(没有 snprintf,没有 strcat,甚至没有 memcpy)。它可能很有用,例如,如果您正在编程一些 libc 不可用的微控制器或操作系统内核。

如果你用谷歌搜索,你可以找到类似的代码。确实它并不比调用 snprintf 复杂多少,而且速度更快。

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

这是另一个稍短的版本。它只是避免了中间索引变量 i 和重复 laste case 代码(但终止字符写了两次)。

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

下面是另一个版本来回答评论说我使用“技巧”来了解输入缓冲区的大小。实际上这不是技巧,而是必要的输入知识(您需要知道要转换的数据的大小)。通过将转换代码提取到单独的函数中,我更清楚地说明了这一点。我还为目标缓冲区添加了边界检查代码,如果我们知道自己在做什么,这并不是必需的。

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

【讨论】:

这不是一个技巧,只是一个常数。在这个问题的上下文中,很明显我们想要转换为十六进制的源的长度是众所周知的(我可以放一些硬编码的 4 而不是 sizeof)。在一般情况下,应该在某个已知长度的输入上调用该函数,并且目标缓冲区有 3 倍 + 1 个字节可用。这必须由调用者确保,转换函数没有理由执行该任务。在某些情况下,调用 strlen() 可能是一种查找源大小的方法,但并非总是如此。如果要转换为十六进制的数字包含零怎么办? 受你的函数启发,我写了一个版本,它还返回写入输出缓冲区的字节数,类似于 snprintf 等。gist.github.com/cellularmitosis/0d8c0abf7f8aa6a2dff3 我认为您应该使用 char str[ sizeof(buf)*3 + 1 ]; 自动使输出缓冲区的大小正确 还有更多的 const 可以保护你。例如“const unsigned char const * p”,这样您就可以确保不写入输入缓冲区。一个使地址(或“指针”)成为常量或变量,另一个使该地址处的内存只读或不只读。通常会阻止您混淆指针。此外,具有有意义的名称来记录哪些缓冲区和指针用于输入和输出也会有所帮助。 @Cecil War:除非我的代码是伪造的,否则使用 const 不会有太多保护,除非你说混合指针或使用相同的指针进行输入和输出(好吧,仍然可能)。但它也将帮助编译器优化代码。更好的是也使用限制关键字(太糟糕了 C99 不是 C++,但通常作为编译器扩展存在)。调用输入缓冲区 in 和输出缓冲区 out 时,您想要什么更有意义?我也可以选择使用字符串并返回一个副本而不是提供输出缓冲区,在现代 C++ 优化器中已经足够好了,不用太在意。【参考方案7】:

这是一种更快的方法:

#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

【讨论】:

此代码包含一个错误,该错误仅在奇怪的不可打印输入上表现出来(还没有时间深入研究数学上到底发生了什么)。尝试对十六进制ca9e3c972f1c5db40c0b4a66ab5bc1a20ca4457bdbe5e0f8925896d5ed37d726 的二进制进行编码,您将得到ÌaÌe3cÌ72f1c5dÌ40c0b4a66Ìb5bÌ1Ì20cÌ4457bÌbÌ5Ì0Ì8Ì258Ì6Ì5Ìd37Ì726。要解决此问题,需要将 for 循环第一行中 hex_str 中的位更改为 @kriss 的答案中的 (input[i] &gt;&gt; 4) &amp; 0x0F。然后它工作正常。 错误 - 不检查 malloc() 失败。 最好在任何地方都使用无符号字符,因为没有人愿意冒有符号字符的风险(一个疯狂的 DEC PDP11 硬件功能),这样你就不会冒有符号比较出错的风险或有符号右移破坏值。在这种情况下,公平地说,代码确实会在任何地方进行防御性的 & 0x0F 来保护你。 bin 输入参数应该是 const unsigned char const * bin,为了这个例程的目的将内存声明为只读。 我已经整合了Cecil Ward的建议,感谢反馈【参考方案8】:

我只是想添加以下内容,即使它稍微偏离主题(不是标准 C),但我发现自己经常寻找它,并且在第一次搜索命中时偶然发现了这个问题。 Linux 内核打印函数printk 也具有格式说明符,用于通过单个格式说明符“直接”输出数组/内存内容:

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(). 

...但是,标准用户空间(s)printf 似乎不存在这些格式说明符。

【讨论】:

【参考方案9】:

我会在此处添加 C++ 版本供感兴趣的人使用。

#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.

它将打印长度为countstd::ostream outbuffer 的十六进制转储(您可以将其默认为std::cout)。每行将包含bytes_per_line 字节,每个字节使用大写的两位十六进制表示。字节之间会有一个空格。在行尾或缓冲区结束时,它将打印一个换行符。如果bytes_per_line 设置为 0,则不会打印 new_line。自己试试吧。

【讨论】:

【参考方案10】:

多么复杂的解决方案! Malloc 和 sprints 和 cast 哦,天哪。 (OZ 报价) 而且在任何地方都没有一个雷姆。天哪 这样的事情怎么样?

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


【讨论】:

好的,现在你有了两个十六进制数字。仍然需要添加分隔符并处理要转换的其他字节。也许有一个循环?让它成为一个函数,你就会得到与我的相似的东西(但相当冗长且难以阅读)。也许你至少应该完成这项工作,然后再在其他海报上点名? 还有一点关于源代码中的 cmets(不是 REM,这是 cmets 的 BASIC 关键字,请避免使用): cmets 用英语说代码在做什么是非常非常糟糕的做法!是的,程序员应该知道模运算符的含义(给出余数)并且除法计算一个数字出现在另一个数字中的次数......并且 printf 打印结果。天哪!【参考方案11】:

为了简单起见,我制作了一个对输入字符串(二进制数据)进行编码的函数:

/* 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;

用法:

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;

【讨论】:

【参考方案12】:

略微修改的 Yannith 版本。 只是我喜欢把它作为返回值

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;

【讨论】:

【参考方案13】:

上面已经存在类似的答案,我添加了这个以解释以下代码行的工作原理:

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

文静难懂,我把解释放在下面的cmets中:

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

【讨论】:

出色的逻辑。正在寻找一个小时来为这个挑战提供一个优雅的非 C++ 字符串答案!【参考方案14】:

此函数适用于用户/调用者希望将十六进制字符串放入字符数组/缓冲区的情况。使用字符缓冲区中的十六进制字符串,用户/调用者可以使用自己的宏/函数将其显示或记录到它想要的任何位置(例如文件)。该函数还允许调用者控制每行放入的(十六进制)字节数。

/**
 * @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));


示例: 代码

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

输出

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 

【讨论】:

【参考方案15】:

基于 Yannuth 的 answer,但经过简化。

这里,dest[] 的长度是len 的两倍,它的分配由调用者管理。

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

【讨论】:

【参考方案16】:

我知道这个问题已经有了答案,但我认为我的解决方案可以帮助某人。

所以,在我的例子中,我有一个表示键的字节数组,我需要将此字节数组转换为十六进制值的 char 数组,以便在一行中打印出来。我将我的代码提取到这样的函数中:

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;

现在,我可以像这样使用我的函数了:

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

Serial 对象是 Arduino 库的一部分,m_publicKey 是我的班级成员,声明如下:uint8_t m_publicKey[32]

【讨论】:

【参考方案17】:

解决方案

函数btox 将任意数据*bb 转换为n 十六进制数字的未终止字符串*xp

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

示例

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

结果:00010A0B

直播:Tio.run.

【讨论】:

【参考方案18】:

你可以用 snprintf 和 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);

输出: bbccdd0fef0f0e0d0c

【讨论】:

以上是关于如何在 C 中将字节数组转换为十六进制字符串?的主要内容,如果未能解决你的问题,请参考以下文章

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

在 C 中将 int 转换为 2 字节的十六进制值

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

如何在Java中将字节数组转换为十六进制格式

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

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