【问题标题】:How to turn a hex string into an unsigned char array?如何将十六进制字符串转换为无符号字符数组?
【发布时间】:2011-03-14 08:46:15
【问题描述】:

例如,我有一个 cstring "E8 48 D8 FF FF 8B 0D"(包括空格)需要转换为等效的 unsigned char 数组 {0xE8,0x48,0xD8,0xFF,0xFF,0x8B,0x0D}。有什么有效的方法来做到这一点?谢谢!

编辑:我不能使用 std 库...所以考虑这是一个 C 问题。对不起!

【问题讨论】:

    标签: c arrays hex


    【解决方案1】:

    这回答了原始问题,该问题要求 C++ 解决方案。

    您可以将istringstreamhex 操纵器一起使用:

    std::string hex_chars("E8 48 D8 FF FF 8B 0D");
    
    std::istringstream hex_chars_stream(hex_chars);
    std::vector<unsigned char> bytes;
    
    unsigned int c;
    while (hex_chars_stream >> std::hex >> c)
    {
        bytes.push_back(c);
    }
    

    注意c 必须是int(或long,或其他整数类型),而不是char;如果它是char(或unsigned char),则会调用错误的&gt;&gt; 重载,并且将从字符串中提取单个字符,而不是十六进制整数字符串。

    额外的错误检查以确保提取的值符合char 是个好主意。

    【讨论】:

    • 因为我不能给出两个正确答案,所以我继续投票,因为这对于 C++ 用户来说绝对是一个很好的解决方案!
    【解决方案2】:

    你永远不会让我相信这个操作是一个性能瓶颈。 有效的方法是使用标准 C 库充分利用您的时间:

    static unsigned char gethex(const char *s, char **endptr) {
      assert(s);
      while (isspace(*s)) s++;
      assert(*s);
      return strtoul(s, endptr, 16);
    }
    
    unsigned char *convert(const char *s, int *length) {
      unsigned char *answer = malloc((strlen(s) + 1) / 3);
      unsigned char *p;
      for (p = answer; *s; p++)
        *p = gethex(s, (char **)&s);
      *length = p - answer;
      return answer;
    }
    

    编译和测试。适用于您的示例。

    【讨论】:

    • 我选择了这个作为答案,因为它只是提供了一个工作示例。谢谢!
    • OTOH,缓冲区溢出“A B C D E F 1 2 3 4 5 6 7 8 9”。
    • 简单得多:for (i=0; i&lt;max &amp;&amp; isxdigit(*s); i++) a[i]=strtol(s, &amp;s, 16); 重点是,您的gethex 函数完全是多余的。 strtol 跳过前导空格本身。如果您想更严格地不接受与模式不匹配的字符串,您可以使用 sscanf 来控制字段宽度并测量匹配长度。
    • @R:关于 strtoul 的好点——我没有仔细阅读手册页。随意编辑。
    • 只有在每两位数字中都有空格的情况下才能正常工作。 IMO 这使得这种方法很糟糕。
    【解决方案3】:
    • 遍历所有字符。
      • 如果您有十六进制数字,则数字为(ch &gt;= 'A')? (ch - 'A' + 10): (ch - '0')
        • 将累加器左移 4 位,然后在新数字中添加(或 OR)。
      • 如果您有空格,而前一个字符不是空格,则将当前累加器值附加到数组并将累加器重置为零。

    【讨论】:

    • +1:这可能是最直接最简单的方法了。
    • 这基本上就是我所做的,除了使用switch而不是三元测试。根据编译器和处理器架构,一种或另一种可能更快。但是你也应该测试每个字符在 0-9A-F 的范围内,它会测试两次相同的东西。
    • @kriss:一切都在假设中。您假设每个值之间必须恰好有两个十六进制数字和一个空格,我的允许省略前导零或多个空格,但假设字符串中没有其他类别的字符。如果你不能假设,我可能会选择单独进行验证,通过测试if (s[strspn(s, " 0123456789ABCDEF")]) /* error */; 当然,这是字符串的另一个传递,但更干净。或者通过在每个字符上使用 isspaceisxdigit 来避免第二次遍历字符串,这使用查找表来提高速度。
    • 循环开关并不是一个真正的问题,我并不认为这是一个区别。我选择假设输入中恰好有两个十六进制字符,因为如果您允许的更多,您还应该检查值的范围。以及允许负数怎么样,我们将不得不管理符号等。 switch is 是一种查找表...(另一种快速转换方法是真正使用一个实现为数组的方法) .
    • 问题指定所有输入都是无符号的。问题没有具体说明总是将零填充到恰好两位数(例如,所有这些都适合0xA0xA0x0A0x000A)或只有一个空格,尽管这些假设是样本输入为真。
    【解决方案4】:

    如果您事先知道要解析的字符串的长度(例如,您正在从 /proc 读取某些内容),您可以使用带有 'hh' 类型修饰符的 sscanf,它指定下一个转换是 diouxX 和指针之一存储它将是有符号字符或无符号字符。

    // example: ipv6 address as seen in /proc/net/if_inet6:
    char myString[] = "fe80000000000000020c29fffe01bafb";
    unsigned char addressBytes[16];
    sscanf(myString, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx
    %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", &addressBytes[0],
    &addressBytes[1], &addressBytes[2], &addressBytes[3], &addressBytes[4], 
    &addressBytes[5], &addressBytes[6], &addressBytes[7], &addressBytes[8], 
    &addressBytes[9], &addressBytes[10], addressBytes[11],&addressBytes[12],
    &addressBytes[13], &addressBytes[14], &addressBytes[15]);
    
    int i;
    for (i = 0; i < 16; i++){
        printf("addressBytes[%d] = %02x\n", i, addressBytes[i]);
    }
    

    输出:

    addressBytes[0] = fe
    addressBytes[1] = 80
    addressBytes[2] = 00
    addressBytes[3] = 00
    addressBytes[4] = 00
    addressBytes[5] = 00
    addressBytes[6] = 00
    addressBytes[7] = 00
    addressBytes[8] = 02
    addressBytes[9] = 0c
    addressBytes[10] = 29
    addressBytes[11] = ff
    addressBytes[12] = fe
    addressBytes[13] = 01
    addressBytes[14] = ba
    addressBytes[15] = fb
    

    【讨论】:

      【解决方案5】:

      使用“旧”的 sscanf() 函数:

      string s_hex = "E8 48 D8 FF FF 8B 0D"; // source string
      char *a_Char = new char( s_hex.length()/3 +1 ); // output char array
      
      for( unsigned i = 0, uchr ; i < s_hex.length() ; i += 3 ) {
          sscanf( s_hex.c_str()+ i, "%2x", &uchr ); // conversion
          a_Char[i/3] = uchr; // save as char
        }
      delete a_Char;
      

      【讨论】:

        【解决方案6】:

        对于纯 C 实现,我认为您可以说服 sscanf(3) 做您想做的事。我相信这应该是可移植的(包括稍微狡猾的类型强制以安抚编译器),只要您的输入字符串只包含两个字符的十六进制值。

        #include <stdio.h>
        #include <stdlib.h>
        
        
        char hex[] = "E8 48 D8 FF FF 8B 0D";
        char *p;
        int cnt = (strlen(hex) + 1) / 3; // Whether or not there's a trailing space
        unsigned char *result = (unsigned char *)malloc(cnt), *r;
        unsigned char c;
        
        for (p = hex, r = result; *p; p += 3) {
            if (sscanf(p, "%02X", (unsigned int *)&c) != 1) {
                break; // Didn't parse as expected
            }
            *r++ = c;
        }
        

        【讨论】:

        • c 声明为unsigned int,否则您可能会覆盖其他局部变量(或者更糟糕的是,您的返回地址)。
        • 但通常 scanf 需要比我的整个答案更长的时间来计算格式代码,而且这个问题确实要求一种有效的方式。
        • @Ben Voigt。是的,但高效是指运行时间还是程序员时间? '-) 无论如何,感谢您指出我应该将c 设为insigned int 并将其强制转换为result 数组。
        • UB。因为在预期结束时p 在终止零之后指向一个字节。
        • @MarekR 不错。写这篇文章(6 年前),我显然有两种想法,声明了一个 cnt 变量,然后没有使用它
        【解决方案7】:

        旧的 C 方式,手动完成 ;-)(有很多更短的方式,但我不是打高尔夫球,我要的是运行时间)。

        enum { NBBYTES = 7 };
        char res[NBBYTES+1];
        const char * c = "E8 48 D8 FF FF 8B 0D";
        const char * p = c;
        int i = 0;
        
        for (i = 0; i < NBBYTES; i++){
            switch (*p){
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
              res[i] = *p - '0';
            break;
            case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
              res[i] = *p - 'A' + 10;
            break;
           default:
             // parse error, throw exception
             ;
           }
           p++;
           switch (*p){
           case '0': case '1': case '2': case '3': case '4':
           case '5': case '6': case '7': case '8': case '9':
              res[i] = res[i]*16 + *p - '0';
           break;
           case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
              res[i] = res[i]*16 + *p - 'A' + 10;
           break;
           default:
              // parse error, throw exception
              ;
           }
           p++;
           if (*p == 0) { continue; }
           if (*p == ' ') { p++; continue; }
           // parse error, throw exception
        }
        
        // let's show the result, C style IO, just cout if you want C++
        for (i = 0 ; i < 7; i++){
           printf("%2.2x ", 0xFF & res[i]);
        }
        printf("\n");
        

        现在另一个允许数字之间有任意数量的数字,任意数量的空格来分隔它们,包括前导或尾随空格(Ben 的规范):

        #include <stdio.h>
        #include <stdlib.h>
        
        int main(){
            enum { NBBYTES = 7 };
            char res[NBBYTES];
            const char * c = "E8 48 D8 FF FF 8B 0D";
            const char * p = c;
            int i = -1;
        
            res[i] = 0;
            char ch = ' ';
            while (ch && i < NBBYTES){
               switch (ch){
               case '0': case '1': case '2': case '3': case '4':
               case '5': case '6': case '7': case '8': case '9':
                  ch -= '0' + 10 - 'A';
               case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
                  ch -= 'A' - 10;
                  res[i] = res[i]*16 + ch;
                  break;
               case ' ':
                 if (*p != ' ') {
                     if (i == NBBYTES-1){
                         printf("parse error, throw exception\n");
                         exit(-1);
                    }
                    res[++i] = 0;
                 }
                 break;
               case 0:
                 break;
               default:
                 printf("parse error, throw exception\n");
                 exit(-1);
               }
               ch = *(p++);
            }
            if (i != NBBYTES-1){
                printf("parse error, throw exception\n");
                exit(-1);
            }
        
           for (i = 0 ; i < 7; i++){
              printf("%2.2x ", 0xFF & res[i]);
           }
           printf("\n");
        }
        

        不,它并没有真正被混淆......但是看起来确实如此。

        【讨论】:

        • 我们可以说'Ick!'吗? (如果只是因为代码会在最后一个循环中“抛出异常”,因为字符串中只有 6 个空格,而不是代码要求的 7 个。)
        • @Jonathan:不再……我也可以添加一个空格来输入。旧分隔符与终结符的争论。
        • 你的小修复没有帮助...*p != ' ' 在终止 NUL 上,你的逻辑或逻辑无关紧要。
        • 哎呀,我又犯错了。您应该更喜欢新修复:-)
        • 有效性检查仍然不稳定。
        猜你喜欢
        • 2017-01-11
        • 2013-03-22
        • 1970-01-01
        • 2017-05-30
        • 1970-01-01
        • 2011-08-05
        • 2013-02-07
        • 2011-08-21
        • 2018-01-31
        相关资源
        最近更新 更多