在C语言中将十六进制转换为字符数组

10

给定一个十六进制值的字符串,例如 "0011223344",其中每两个字符代表一个十六进制值,如 0x00、0x11 等等。

我该如何将这些值添加到 char 数组中?

相当于说:

char array[4] = { 0x00, 0x11 ... };
11个回答

18

你不能将 5 字节的数据存入一个 4 字节的数组中;这会导致缓冲区溢出。

如果你有一个十六进制数字的字符串,可以使用 sscanf() 和一个循环:

#include <stdio.h>
#include <ctype.h>

int main()
{
    const char *src = "0011223344";
    char buffer[5];
    char *dst = buffer;
    char *end = buffer + sizeof(buffer);
    unsigned int u;

    while (dst < end && sscanf(src, "%2x", &u) == 1)
    {
        *dst++ = u;
        src += 2;
    }

    for (dst = buffer; dst < end; dst++)
        printf("%d: %c (%d, 0x%02x)\n", dst - buffer,
               (isprint(*dst) ? *dst : '.'), *dst, *dst);

    return(0);
}

注意打印以零字节开头的字符串需要小心处理;大多数操作会在遇到第一个空字节时终止。请注意,此代码未对缓冲区进行空字符终止;不清楚是否需要空字符终止,并且我声明的缓冲区没有足够的空间添加终止符号(但很容易修复)。如果将代码打包为子程序,则有很大机会需要返回转换后字符串的长度(尽管您也可以认为它是源字符串长度除以二的长度)。


2
这个答案为我节省了数小时的工作时间!!!对于Arduino也非常有效,只需省略printf部分即可。 - frazras

3
如果字符串正确且不需要保留其内容,则可以使用以下方法进行操作:
#define hex(c) ((*(c)>='a')?*(c)-'a'+10:(*(c)>='A')?*(c)-'A'+10:*(c)-'0') 

void hex2char( char *to ){
  for(char *from=to; *from; from+=2) *to++=hex(from)*16+hex(from+1);
  *to=0;
}

编辑1:抱歉,我忘记计算字母A-F(a-f)的值。

编辑2:我尝试编写更加严谨的代码:

#include <string.h> 

int xdigit( char digit ){
  int val;
       if( '0' <= digit && digit <= '9' ) val = digit -'0';
  else if( 'a' <= digit && digit <= 'f' ) val = digit -'a'+10;
  else if( 'A' <= digit && digit <= 'F' ) val = digit -'A'+10;
  else                                    val = -1;
  return val;
}

int xstr2str( char *buf, unsigned bufsize, const char *in ){
  if( !in ) return -1; // missing input string

  unsigned inlen=strlen(in);
  if( inlen%2 != 0 ) return -2; // hex string must even sized

  for( unsigned i=0; i<inlen; i++ )
    if( xdigit(in[i])<0 ) return -3; // bad character in hex string

  if( !buf || bufsize<inlen/2+1 ) return -4; // no buffer or too small

  for( unsigned i=0,j=0; i<inlen; i+=2,j++ )
    buf[j] = xdigit(in[i])*16 + xdigit(in[i+1]);

  buf[inlen/2] = '\0';
  return inlen/2+1;
}

测试:

#include <stdio.h> 

char buf[100] = "test";

void test( char *buf, const char *s ){
   printf("%3i=xstr2str( \"%s\", 100, \"%s\" )\n", xstr2str( buf, 100, s ), buf, s );
}

int main(){
  test( buf,      (char*)0   );
  test( buf,      "123"      );
  test( buf,      "3x"       );
  test( (char*)0, ""         );
  test( buf,      ""         );
  test( buf,      "3C3e"     );
  test( buf,      "3c31323e" );

  strcpy( buf,    "616263"   ); test( buf, buf );
}

结果:

 -1=xstr2str( "test", 100, "(null)" )
 -2=xstr2str( "test", 100, "123" )
 -3=xstr2str( "test", 100, "3x" )
 -4=xstr2str( "(null)", 100, "" )
  1=xstr2str( "", 100, "" )
  3=xstr2str( "", 100, "3C3e" )
  5=xstr2str( "", 100, "3c31323e" )
  4=xstr2str( "abc", 100, "abc" )

假设您被允许修改字符串,并且在原地进行翻译,并且将转换后的字符串以空终止。由于第一个字节为空,您可能需要返回转换后的字符数。 - Jonathan Leffler
你说得没错,但问题没有明确的要求,所以这段代码已经足够好了;-) - sambowry
你可能也想考虑支持超过9的十六进制数字。如果问题中所给出的只是需要工作的字符串,那么显然最简洁的答案是char array[] = {0, 17, 34, 51, 68};。但我认为提问者在说"i.e."时实际上是指"e.g."。 - Steve Jessop

3
我会这样做:
// Convert from ascii hex representation to binary
// Examples;
//   "00" -> 0
//   "2a" -> 42
//   "ff" -> 255
// Case insensitive, 2 characters of input required, no error checking
int hex2bin( const char *s )
{
    int ret=0;
    int i;
    for( i=0; i<2; i++ )
    {
        char c = *s++;
        int n=0;
        if( '0'<=c && c<='9' )
            n = c-'0';
        else if( 'a'<=c && c<='f' )
            n = 10 + c-'a';
        else if( 'A'<=c && c<='F' )
            n = 10 + c-'A';
        ret = n + ret*16;
    }
    return ret;
}

int main()
{
    const char *in = "0011223344";
    char out[5];
    int i;

    // Hex to binary conversion loop. For example;
    // If in="0011223344" set out[] to {0x00,0x11,0x22,0x33,0x44}
    for( i=0; i<5; i++ )
    {
        out[i] = hex2bin( in );
        in += 2;
    }
    return 0;
}

1
假设这是一个小端 ASCII 平台。也许 OP 指的是“字符数组”,而不是“字符串”。我们使用 char 和位掩码的配对工作.. 注意 x16 的移位。
/* not my original work, on stacko somewhere ? */

for (i=0;i < 4;i++) {

    char a = string[2 * i];
    char b = string[2 * i + 1];

    array[i] = (((encode(a) * 16) & 0xF0) + (encode(b) & 0x0F));
 }

并且函数encode()被定义...

unsigned char encode(char x) {     /* Function to encode a hex character */
/****************************************************************************
 * these offsets should all be decimal ..x validated for hex..              *
 ****************************************************************************/
    if (x >= '0' && x <= '9')         /* 0-9 is offset by hex 30 */
        return (x - 0x30);
    else if (x >= 'a' && x <= 'f')    /* a-f offset by hex 57 */
        return(x - 0x57);
    else if (x >= 'A' && x <= 'F')    /* A-F offset by hex 37 */
        return(x - 0x37);
}

这种方法在其他地方已经流传,不是我的原创作品,而且它很老了。 由于不可移植,不受纯粹主义者的喜爱,但扩展会很简单。

你能解释一下,你是如何知道 hex(0x30,0x57,0x37) 这个提取过程的吗? - Faruk
对于ASCII字符集,它们是连续的(这对于EBCDIC不起作用,需要进行更多测试)。请参考任何ASCII表。'0'是48。从'0'(char)中减去48会得到0(integer)。48是0x30(3x16)+(0x1)。是的,它们应该是十进制值,我很抱歉。如果我真的很懒,我就会直接使用MySQL中的unhex()或“利用”该源。 - mckenzm

1
我正在寻找类似的东西,在阅读了很多后,最终创建了这个函数。认为它可能会对某些人有帮助。
// in = "63 09  58  81" 
void hexatoascii(char *in, char* out, int len){
    char buf[5000];
    int i,j=0;
    char * data[5000];
    printf("\n size %d", strlen(in));
    for (i = 0; i < strlen(in); i+=2)
    {
        data[j] = (char*)malloc(8);
        if (in[i] == ' '){
            i++;
        }
        else if(in[i + 1] == ' '){
            i++;
        }
        printf("\n %c%c", in[i],in[i+1]);
        sprintf(data[j], "%c%c", in[i], in[i+1]);
        j++;
    }

    for (i = 0; i < j-1; i++){
        int tmp;
        printf("\n data %s", data[i] );
        sscanf(data[i], "%2x", &tmp);
        out[i] = tmp;
    }
    //printf("\n ascii value of hexa %s", out);
}

1
我知道的最好方法:

int hex2bin_by_zibri(char *source_str, char *dest_buffer)
{
  char *line = source_str;
  char *data = line;
  int offset;
  int read_byte;
  int data_len = 0;

  while (sscanf(data, " %02x%n", &read_byte, &offset) == 1) {
    dest_buffer[data_len++] = read_byte;
    data += offset;
  }
  return data_len;
}

该函数返回保存在dest_buffer中的转换字节数。输入字符串可以包含空格和大小写字母。
"01 02 03 04 ab Cd eF garbage AB"
翻译为dest_buffer包含 01 02 03 04 ab cd ef
并且也可以翻译为 "01020304abCdeFgarbageAB"
与之前相同。
解析在第一个“错误”(非十六进制,非空格)处停止。
注意:这也是一个有效的字符串:
"01 2 03 04 ab Cd eF garbage AB"
并且产生:
01 02 03 04 ab cd ef

0

提供最佳方法:

将十六进制字符串转换为数值,例如 str[] = "0011223344" 转换为值 0x0011223344,使用以下方法:

value = strtoul(string, NULL, 16); // or strtoull()

完成。如果需要删除开头的0x00,请参见下文。

对于LITTLE_ENDIAN平台,还需注意以下内容: 将十六进制值转换为字符数组,例如将值0x11223344转换为char arr[N] = {0x00, 0x11, ...}

unsigned long *hex = (unsigned long*)arr;
*hex = htonl(value);
// you'd like to remove any beginning 0x00
char *zero = arr;
while (0x00 == *zero) { zero++; }
if (zero > arr) memmove(zero, arr, sizeof(arr) - (zero - arr));

完成。

注: 在32位系统上将长字符串转换为64位十六进制字符数组时,应使用unsigned long long而不是unsigned long,并且htonl不足以完成此操作,因此请按照以下方式自行完成,因为可能没有htonll、htonq或hton64等函数:

#if __KERNEL__
    /* Linux Kernel space */
    #if defined(__LITTLE_ENDIAN_BITFIELD)
        #define hton64(x)   __swab64(x)
    #else
        #define hton64(x)   (x)
    #endif
#elif defined(__GNUC__)
    /* GNU, user space */
    #if __BYTE_ORDER == __LITTLE_ENDIAN 
        #define hton64(x)   __bswap_64(x)
    #else
        #define hton64(x)   (x)
    #endif
#elif 
         ...
#endif

#define ntoh64(x)   hton64(x)

看一下http://effocore.googlecode.com/svn/trunk/devel/effo/codebase/builtin/include/impl/sys/bswap.h


支持的最大十六进制字符串长度为16个字节/字符,当第一个字符不是'0'时。 - Test

0

致命错误...

有几种方法可以做到这一点...首先,您可以使用memcpy()将精确表示复制到char数组中。

您也可以使用位移和位掩码技术。我猜这就是你需要做的,因为它听起来像是一个作业问题。

最后,您可以使用一些花哨的指针间接引用来复制所需的内存位置。

所有这些方法都在这里详细说明:

如何在char数组中存储int?


你能否解释一下memcpy()函数的使用方法? - Jonathan Leffler

0
{
    char szVal[] = "268484927472";
    char szOutput[30];

    size_t nLen = strlen(szVal);
    // Make sure it is even.
    if ((nLen % 2) == 1)
    {
        printf("Error string must be even number of digits %s", szVal);
    }

    // Process each set of characters as a single character.
    nLen >>= 1;
    for (size_t idx = 0; idx < nLen; idx++)
    {
        char acTmp[3];
        sscanf(szVal + (idx << 1), "%2s", acTmp);
        szOutput[idx] = (char)strtol(acTmp, NULL, 16);
    }
}

0
以下是我的hex2binbin2hex实现。
这些函数:
  • 属于公共领域(可以自由复制和粘贴)
  • 非常简单易懂
  • 经过正确的测试验证
  • 进行错误处理(-1表示无效的十六进制字符串)

hex2bin

static char h2b(char c) {
    return '0'<=c && c<='9' ? c - '0'      :
           'A'<=c && c<='F' ? c - 'A' + 10 :
           'a'<=c && c<='f' ? c - 'a' + 10 :
           /* else */         -1;
}

int hex2bin(unsigned char* bin,  unsigned int bin_len, const char* hex) {
    for(unsigned int i=0; i<bin_len; i++) {
        char b[2] = {h2b(hex[2*i+0]), h2b(hex[2*i+1])};
        if(b[0]<0 || b[1]<0) return -1;
        bin[i] = b[0]*16 + b[1];
    }
    return 0;
}

bin2hex

static char b2h(unsigned char b, int upper) {
    return b<10 ? '0'+b : (upper?'A':'a')+b-10;
}

void bin2hex(char* hex, const unsigned char* bin, unsigned int bin_len, int upper) {
    for(unsigned int i=0; i<bin_len; i++) {
        hex[2*i+0] = b2h(bin[i]>>4,   upper);
        hex[2*i+1] = b2h(bin[i]&0x0F, upper);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接