如何有效地将十六进制编码的字符串转换为 C 中的字符串

How can I convert hex encoded string to string in C efficiently

我需要像这样转换十六进制编码的字符串:

char hstr[9] = "61626364"; // characters abcd[=10=]

进入

"abcd" // characters as hex: 0x61 0x62 0x63 0x64
       // hex "digits" a-f are always lowercase

此刻我写了这个函数:

#include <stdlib.h>

void htostr(char* hexstr, char* str) {
    int len = strlen(hexstr);

    for (int i = 0; i < len/2; i++) // edit: fixed bounds
    {
        char input[3] = { hexstr[2 * i], hexstr[2 * i + 1], 0 };
        *(str + i) = (char)strtol(input, NULL, 16);
    }
}

我正在使用 strtol 函数来完成这项工作。

我觉得我正在为 input 数组浪费 3 个字节的内存和一些处理器时间来复制两个字节并以 0 结尾,因为 strtol 函数没有像“长度”这样的参数。

代码应该在一个非常繁忙的微控制器上 运行,字符串很长(尽快释放 hexstr 使用的内存是个好主意) .

问题是:有没有更有效的方法来做到这一点而无需从头开始编写我自己的转换器?

“从头开始”是指不使用函数标准库的低级转换。

允许临时更改输入字符串时:

void htostr_1(char* hexstr, char* str) {
    int len = strlen(hexstr);

    for (int i = 0; 2 * i + 2 <= len; i++)
    {
        char tmp = hexstr[2 * i + 2];
        hexstr[2 * i + 2] = 0;
        str[i] = (char)strtol(hexstr + 2 * i, NULL, 16);
        hexstr[2 * i + 2] = tmp;
    }
}

在终止字符串之前保存下一个字节以在 strtol 之后撤消它:https://godbolt.org/z/zdMdKrY7n

附带说明:for循环的结束条件错误,你越界访问:https://godbolt.org/z/ra87cWocY

如果您还想保存 int len 和不必要的 strlen 调用:

void htostr_2(char* hexstr, char* str) {
    while (*hexstr)
    {
        char tmp = hexstr[2];
        hexstr[2] = 0;
        *str++ = (char)strtol(hexstr, NULL, 16);
        hexstr[2] = tmp;
        hexstr += 2;
    }
}

您可以创建一个函数来转换字符 0 .. 9A .. [=17=,而不是复制两个字符并使用 strtol ] 到 int0x00xF)。

#include <ctype.h>

int toval(char ch) {
    if (isdigit((unsigned char)ch)) return ch - '0';
    return toupper((unsigned char)ch) - 'A' + 0x10;
}

然后遍历字符串并将结果相加会非常简单:

void htostr(char *wr, const char *rd) {
    for (; rd[0] != '[=11=]' && rd[1] != '[=11=]'; rd += 2, ++wr) {
        // multiply the first with 0x10 and add the value of the second
        *wr = toval(rd[0]) * 0x10 + toval(rd[1]);
    }
    *wr = '[=11=]'; // null terminate
}

用法示例:

#include <stdio.h>

int main() {
    char hstr[] = "61626364";
    char res[1 + sizeof hstr / 2];

    htostr(res, hstr);

    printf(">%s<\n", res);
}

如果你真的想trim它下来:

void htostr(char* hexstr, char* str) {
    int i = 0;

    while (hexstr[2*i]) {
    {
        str[i] = 0;
        for (int j=0; j<2; j++) {
            str[i] <<= 4;
            char c = hexstr[2*i+j];
            if (c >= '0' && c <= '9')  {
                str[i] |= c - '0';
            } else if (c >= 'A' && c <= 'F')  {
                str[i] |= c - 'A' + 10;
            } else if (c >= 'a' && c <= 'f')  {
                str[i] |= c - 'a' + 10;
            }
        }
        i++;
    }
}

有很多方法可以做到这一点,高效取决于典型的字符串长度、使用频率、允许的内存占用等。

下面是一个相当快的工作。

遍历成对的十六进制数字并通过 table look-up.

计算字符代码
#include <ctype.h>

static const unsigned char val[] = { //
    ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, //
    ['5'] = 5, ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, //
    ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, //
    ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, //
};

void htostr_alt(const char* hexstr, char* str) {
  // Best to use is...() functions with unsigned char data
  const unsigned char *uhexstr = (const unsigned char *) hexstr; 

  while (isxdigit(uhexstr[0]) && isxdigit(uhexstr[1])) {
    *str++ = (char) (val[uhexstr[0]]*16u + uhexstr[uhexstr[1]]);
    uhexstr += 2;
  }
  *str = '[=10=]';

  // Consider returning something useful, like where did input stop.
  // return (char *) uhexstr;
}

为避免分配字符时实现定义的行为:

void htostr_alt2(const char* hexstr, char* str) {
  const unsigned char *uhexstr = (const unsigned char *) hexstr; 
  unsigned char *ustr = (const unsigned char *) str; 

  while (isxdigit(uhexstr[0]) && isxdigit(uhexstr[1])) {
    *ustr++ = (unsigned char) (val[uhexstr[0]]*16u + uhexstr[uhexstr[1]]);
    uhexstr += 2;
  }
  *ustr = '[=11=]';
}

即使字符串长度超过 INT_MAX,代码也能正常工作,接受 const 输入字符串,在任何 non-hex-digit 对上停止,并且只有 1 次通过源字符串。

如果您不喜欢 isxdigit() 函数,编码 unsigned char my_isxdigit[256].

很简单

假设您事先知道字符串格式并且它永远不会超过 8 位数字,那么请保持简单。这既高效又可读:

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

int main()
{
  char hstr[9] = "61626364";
  uint32_t n = strtoul(hstr, 0, 16);
  char str[5] = 
  {
    (n >> 24) & 0xFFu,
    (n >> 16) & 0xFFu,
    (n >>  8) & 0xFFu,
    (n >>  0) & 0xFFu,
    '[=10=]'
  };
  puts(str);
}

至于手动推出十六进制字符串到整数的转换(我真的不明白你为什么会在这种情况下),最有效但稍微消耗闪存的代码是这样的:

const uint8_t LUT[128] =
{
  ['0'] =  0, ['1'] =  1, /* and so on... */ 
  ['A'] = 10, ['B'] = 11, /* and so on... */
};

...
uint8_t val = LUT[str[i]];