将十六进制字符串转换为字节字符串

6

我需要将类似于 char * s = "2f0a3f" 的字符串转换为其代表的实际字节,当从十六进制表示解码时。目前我正在使用以下方法,但感觉很笨拙而且不正确。

  size_t hexlength = strlen(s);
  size_t binlength = hexlength / 2;

  unsigned char * buffer = malloc(binlength);
  long i = 0;
  char a, b;

  for (; i < hexlength; i += 2) {
    a = s[i + 0]; b = s[i + 1];
    buffer[i / 2] =
      ((a < '9' ? a - '0' : a - 'a' + 10) << 4) + (b < '9' ? b - '0' : b - 'a' + 10);
  }

这里有两件事让我感到不太好:

  1. 每次将数据推入缓冲区时,我都要除以二
  2. 找出十六进制数字的小数值时需要条件逻辑判断

是否有更好的解决方法?最好不要使用需要添加依赖项的东西(因为我想尽量减少跨平台问题)。我的位运算很糟糕 ;)

注意:数据已预先验证为全小写且为正确的十六进制对字符串。


你是在使用I/O将其发送到某个地方吗?如果是的话,你可以使用%u代替%d。 - David
我知道在 C++ 中,你可以简单地输出十六进制值作为它们的字符串组件,也许如果你将一个十六进制值传递给 printf(),或者在你的情况下像 sprintf() 这样的缓冲区打印,它会进行转换? - im so confused
实际字节是什么意思?是以位形式表示还是字符形式?我通常用十六进制读取字节,所以想问一下。 - im so confused
我指的是二进制字符串,就好像我从磁盘文件中读取它一样。数据以十六进制格式(ASCII安全)传输,但在我将其用作实际字节流之前,我需要对其进行解码,以便例如,如果这是JPEG数据,我可以将其写入文件,然后打开该文件并查看JPEG。 - d11wtq
BSD提供了digittoint。您可能希望将该逻辑拆分为具有该名称的宏或内联函数。 - ecatmur
关于1:你可以a)除以二,b)乘以二,c)保留两个索引变量。关于2:使用查找表怎么样? - Daniel Fischer
7个回答

6
/* allocate the buffer */
char * buffer = malloc((strlen(s) / 2) + 1);

char *h = s; /* this will walk through the hex string */
char *b = buffer; /* point inside the buffer */

/* offset into this string is the numeric value */
char xlate[] = "0123456789abcdef";

for ( ; *h; h += 2, ++b) /* go by twos through the hex string */
   *b = ((strchr(xlate, *h) - xlate) * 16) /* multiply leading digit by 16 */
       + ((strchr(xlate, *(h+1)) - xlate));

编辑添加

在80x86汇编语言中,strchr()的核心基本上是一条指令 - 它不会循环。

另外:这种方法没有边界检查,不能用于Unicode控制台输入,并且如果传递无效字符,则会崩溃。

还有:感谢那些指出了一些严重错误的人。


哇,这绝对更短。我得检查一下 strspn 是干什么的...如果它每次迭代 xlate 字符串来查找索引,那可能有点低效,除非编译器很聪明并且能够缓存结果。 - d11wtq
好的,strspn 可能效率不高,但它确实给了我一个想法,如果我们预先用十进制值填充一个 char[256],只在 '0' - '9' 和 'a' - 'f' 的索引处设置,迭代次数可以减少多达 16 次。 - d11wtq
@d11wtq 你可以轻松地使用 char [128]。所有内容都在ASCII范围内。 - Daniel Fischer
@Morpfh - 这些都应该已经修复了,谢谢。我还没有通过实际编译器运行它... - egrunin
1
-1:这不起作用。你应该减去 xlate 而不是 s - Jon Cage
@JonCage:已修复,谢谢--最终将其编译通过。我可以拿回我的分数吗?:) - egrunin

4

虽然这样做可能没有太大的区别,但我会选择乘法而不是除法。此外,值得注意的是,将数字代码拆分出来会更好,因为您可能希望将其移植到字符集中 a-f 不相邻的平台上(只是开个玩笑!)

  inline int digittoint(char d) {
    return ((d) <= '9' ? (d) - '0' : (d) - 'a' + 10);
  }
  #define digittoint(d) ((d) <= '9' ? (d) - '0' : (d) - 'a' + 10)

  size_t hexlength = strlen(s);
  size_t binlength = hexlength / 2;

  unsigned char * buffer = malloc(binlength);
  long i = 0;
  char a, b;

  for (; i < binlength; ++i) {
    a = s[2 * i + 0]; b = s[2 * i + 1];
    buffer[i] = (digittoint(a) << 4) | digittoint(b);
  }

我已经修复了您的数字转整数实现中的一个错误,并用位或符号替换了+,因为它更能表达您的意图。

然后,您可以尝试找到最佳的digittoint实现方式 - 像上面的条件算术、strspn或查找表。

这里有一个可能的无分支实现-奖励!-适用于大写字母:

inline int digittoint(char d) {
    return (d & 0x1f) + ((d >> 6) * 0x19) - 0x10;
}

1

尝试类似这样的东西:

const unsigned char bin[128] =
{
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

int hexlength = strlen(s); 
int binlength = (hexlength / 2); 

unsigned char * buffer = (unsigned char *) malloc(binlength); 
if (buffer)
{
    char *hex = s; 

    unsigned char *buf = buffer;
    unsigned char b, c;

    int ok = 1;

    for (int i = 0; i < hexlength; i += 2)
    { 
        b = bin[*hex++];
        c = bin[*hex++];

        if ((b == -1) || (c == -1))
        {
            ok = 0;
            break;
        )

        *buf++ = ((b << 4) | c); 
    }

    if (ok == 1)
    {
        // use buffer as needed, up to binlength number of bytes...
    }

    free(buffer);
} 

0
我想出了一个更简单的函数,它获取字符串并将转换结果逐字节复制到给定大小为N的字节数组中,并进行边界和完整性检查。
int8_t convert_str_to_bytes(uint8_t *byte_array, char* str, size_t n)
{
    char *hex_match = "0123456789ABCDEF";
    int i, j = 0;
    char cbuf[3];
    long ibuf;

    if (strlen(str) < n) {
            printf("ERROR: String is shorter than specified size.\n");
            return -1;
    }

    for (i = 0; i < n; i += 2) {

            strncpy(cbuf, &str[i], 2);

            if (strspn(cbuf, hex_match) != 2) {
                    printf("ERROR: String is not a hexadecimal representation. Breaking now...\n");
                    return -1;
            }

            ibuf = strtol(cbuf, NULL, 16);

            byte_array[j] = (uint8_t)ibuf;
            ++j;
    }

    return 0;
}

0

这里有一些小的改进,以符合MISRA标准。名称很令人困惑。

static inline uint8_t HexcharToInt(char c) {
    char result = 16;
    if (('0' <= c) && (c <= '9')) {
        result = c - '0';
    } else if (('a' <= c) && (c <= 'f')) {
        result = c + 10 - 'a';
    } else if (('A' <= c) && (c <= 'F')) {
        result = c + 10 - 'A';
    }
    return (uint8_t) result;
}

uint8_t *array = NULL;

size_t hexstringToArray(char *hexstring) {
    size_t len    = (strlen(hexstring) + 1) / 2; // Aufrunden
    if (array != NULL) {
        free(array);
        array = NULL;
    }
    array = (uint8_t*) malloc(len);
    uint8_t *arr = array;
    for (size_t i = 0; (i < len) && (len > 0); i++) {
        *arr = 0U;
        for (uint8_t shift = 8U; (shift > 0U) && (len > 0); ) {
            shift -= 4U;
            uint8_t curInt = HexcharToInt(*hexstring++);
            if (curInt >= 16U) {
                len = 0;
            } else {
                *arr |= ((uint8_t) curInt << shift);
            }
        }
        arr++;
    }
    return len;
}

0
如果您需要将十六进制字符串转换为十进制数字,可以使用atol()sprintf()
如果您需要逐字节进行转换,可以缓冲每个字节,并在填充每个缓冲区时通过sprintf传递它们。
char *hexRep;
char *decRep;
long int decVal;
...
decVal = atol(hexRep);
sprintf(decRep, "%u", decVal);

这两个都在C的标准库中。在获取每个字节的字符串表示后,您可以使用strcat()将它们直接连接在一起。


1
“(可能非常长的)字符串”无法使用strtol - Daniel Fischer
1
这不会返回一个数字吗?我需要一个字符串。 - d11wtq
我没有注意到那个要求!我会用不同的答案来更新它。 - Ben Richards

-1
inline char HexToChar(char c)
{
    if ('0' <= c && c <= '9')
    {
        return c - '0';
    }
    else if ('a' <= c && c <= 'f')
    {
        return c + 10 - 'a';
    }
    else if ('A' <= c && c <= 'F')
    {
        return c + 10 - 'A';
    }

    return -1;
}

size_t HexToBinrary( const char* hex, size_t length, char* binrary, size_t binrary_cap )
{
    if (length % 2 != 0 || binrary_cap < length / 2)
    {
        return 0;
    }

    memset(binrary, 0, binrary_cap);
    size_t n = 0;
    for (size_t i = 0; i < length; i += 2, ++n)
    {
        char high = HexToChar(hex[i]);
        if (high < 0)
        {
            return 0;
        }

        char low = HexToChar(hex[i + 1]);
        if (low < 0)
        {
            return 0;
        }

        binrary[n] = high << 4 | low;
    }
    return n;
}

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