如何在C语言中高效地将十六进制字符串转换为整数?

28

在C语言中,将一串十六进制数字转换为二进制的最有效的方法是什么?可以得到一个unsigned int或者unsigned long类型的值。

例如,如果有一个字符串0xFFFFFFFE,希望得到一个int类型的值4294967294

16个回答

41

33

编辑:现在与MSVC、C++和非GNU编译器兼容(请参见结尾)。

问题是"最有效的方法"。OP没有指定平台,他可以为一个具有256字节闪存的基于RISC的ATMEL芯片进行编译。

为了记录,并且对于那些像我一样欣赏“最简单的方法”和“最有效的方法”之间差异的人,以及那些喜欢学习的人...

static const long hextable[] = {
   [0 ... 255] = -1, // bit aligned access into this table is considerably
   ['0'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, // faster for most modern processors,
   ['A'] = 10, 11, 12, 13, 14, 15,       // for the space conscious, reduce to
   ['a'] = 10, 11, 12, 13, 14, 15        // signed char.
};

/** 
 * @brief convert a hexidecimal string to a signed long
 * will not produce or process negative numbers except 
 * to signal error.
 * 
 * @param hex without decoration, case insensitive. 
 * 
 * @return -1 on error, or result (max (sizeof(long)*8)-1 bits)
 */
long hexdec(unsigned const char *hex) {
   long ret = 0; 
   while (*hex && ret >= 0) {
      ret = (ret << 4) | hextable[*hex++];
   }
   return ret; 
}

它不需要外部库,速度非常快。它可以处理大写、小写、无效字符、奇数长度的十六进制输入(例如:0xfff),并且最大尺寸仅受编译器限制。

对于不支持GCC或C++编译器或无法接受fancy hextable声明的编译器。

请将第一条语句替换为以下较长但更符合规范的版本:

static const long hextable[] = { 
    -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,-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,-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,-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
};

它无法通过Android ndk-build编译。 - hB0
@hB0 我会以同样的方式回应那个非常模糊和毫无意义的观察:它在clang上编译得很好。虽然有22个警告,但这是可以预料的。 - Orwellophile
我在Android NDK中使用了ndk-build工具 - http://developer.android.com/tools/sdk/ndk/index.html,但它无法编译,特别是在数组声明上出现错误。虽然我很喜欢这段代码片段,但我无法使用它,所以不得不使用其他好的方法(但效率低下)。现在无法给你精确的编译错误..(上次已经给过你+1了) - hB0
@hB0 只需将代码第二行带有"[0..255]"的部分注释掉,然后祈祷您永远不会收到无效输入。 - Orwellophile
@Alex 为什么不需要大量转换的人会关心找到最有效的算法呢? - Orwellophile
显示剩余9条评论

25

试试这个:

#include <stdio.h>
int main()
{
    char s[] = "fffffffe";
    int x;
    sscanf(s, "%x", &x);
    printf("%u\n", x);
}

5
太棒了!我以前从未见过这种方法。 - Cloud Cho

8

对于AVR微控制器,我编写了以下函数,包括相关注释以使其易于理解:

/**
 * hex2int
 * take a hex string and convert it to a 32bit number (max 8 hex digits)
 */
uint32_t hex2int(char *hex) {
    uint32_t val = 0;
    while (*hex) {
        // get current character then increment
        char byte = *hex++; 
        // transform hex character to the 4bit equivalent number, using the ascii table indexes
        if (byte >= '0' && byte <= '9') byte = byte - '0';
        else if (byte >= 'a' && byte <='f') byte = byte - 'a' + 10;
        else if (byte >= 'A' && byte <='F') byte = byte - 'A' + 10;    
        // shift 4 to make space for new digit, and add the 4 bits of the new digit 
        val = (val << 4) | (byte & 0xF);
    }
    return val;
}

例子:

char *z ="82ABC1EF";
uint32_t x = hex2int(z);
printf("Number is [%X]\n", x);

将输出: 在此输入图片描述

我不这么认为,但也许你忘记提供一些参数了。 - radhoo

7
如果您没有标准库,那么您必须手动操作。
unsigned long hex2int(char *a, unsigned int len)
{
    int i;
    unsigned long val = 0;

    for(i=0;i<len;i++)
       if(a[i] <= 57)
        val += (a[i]-48)*(1<<(4*(len-1-i)));
       else
        val += (a[i]-55)*(1<<(4*(len-1-i)));

    return val;
}

注意:此代码假定使用大写字母A-F。如果len超过您的最长整数32或64位,则无法正常工作,并且没有对非法十六进制字符进行错误捕获。


2
a[i]-'0'a[i]-'A'+10 在罕见情况下,如果您的系统使用 EBCDIC(它们仍然存在),也可以使用。 - Patrick Schlüter
2
'0''A'也可以使你的代码自我记录,方便那些不熟悉ASCII表的人。 - Peter Cordes

5
通常情况下,您的问题存在严重的术语错误或不明确。在普通语言中,这通常并不重要,但在特定问题的背景下,它至关重要。您需要知道,“十六进制值”和“十进制值”(或“十六进制数”和“十进制数”)不存在。 “十六进制”和“十进制”是值的表示方式的属性。同时,值(或数字)本身没有表示,因此它们不能是“十六进制”或“十进制”。例如,在C语法中,0xF和15是相同数字的两个不同表示。
我猜您的问题是想将一个ASCII十六进制表示的值(即一个字符串)转换为ASCII十进制表示的值(另一个字符串)。一种方法是使用整数表示作为中间表示:首先,使用“strto…”组中的函数(如“strtol”)将ASCII十六进制表示转换为足够大小的整数,然后使用“sprintf”将该整数转换为ASCII十进制表示。
如果这不是您需要做的事情,请澄清您的问题,因为根据您的问题陈述方式无法解决问题。

我也将问题理解为十六进制字符串->十进制字符串,但这与其他答案不符。我编辑了问题以匹配被接受的答案和大多数其他答案。字符串->字符串的问题很难懂,但让我想知道是否可以在不通过二进制整数作为中间步骤的情况下完成(例如对于无法适应uint64_t的数字)。然而,使用带进位加法处理一串十进制数字非常麻烦,所以可能行不通。 - Peter Cordes

3

如前所述,效率基本上取决于优化的目标。

当优化代码行数或者在缺乏完整标准库的环境中工作时,有一种快捷而粗略的选择:

// makes a number from two ascii hexa characters
int ahex2int(char a, char b){

    a = (a <= '9') ? a - '0' : (a & 0x7) + 9;
    b = (b <= '9') ? b - '0' : (b & 0x7) + 9;

    return (a << 4) + b;
}

更多类似的线程可以在这里找到:https://dev59.com/Hmkw5IYBdhLWcg3wE2dA#58253380


2

十六进制转十进制。不要在在线编译器上运行它,因为它不会起作用。

#include<stdio.h>
void main()
{
    unsigned int i;
    scanf("%x",&i);
    printf("%d",i);
}

1
这个程序可以处理大小写字母......我亲自测试过,它可以工作。 - rishabh kedia

2

@Eric

我本来希望看到一个C语言巨匠发布一些非常酷的东西,有点像我做的但不那么冗长,同时仍然“手动”完成。

嗯,我不是C语言大师,但这是我想出来的:

unsigned int parseHex(const char * str)
{
    unsigned int val = 0;
    char c;

    while(c = *str++)
    {
        val <<= 4;

        if (c >= '0' && c <= '9')
        {
            val += c & 0x0F;
            continue;
        }

        c &= 0xDF;
        if (c >= 'A' && c <= 'F')
        {
            val += (c & 0x07) + 9;
            continue;
        }

        errno = EINVAL;
        return 0;
    }

    return val;
}

我最初使用了更多的位掩码操作而非比较,但我严重怀疑在现代硬件上位掩码操作是否比比较更快。


四个抱怨:1)它无法编译。2)它不能处理小写字母。3)它不工作(A => 1)。4)无效字符只是被忽略!你测试过它了吗? - Martin York
你读过了吗?“我实际上没有编译过这个,所以我可能犯了一些相当大的错误。” 所以,不,我没有测试它。 - Derek Park
好的,我修复了它。值得一提的是,它已经通过“c&= 0xDF”语句处理了小写字母。但是它在其他多个方面都有问题。 - Derek Park
第五个投诉:如果您使用 ANSI C 进行编程(并且不能保证具有基于 ASCII 的执行字符集),则无法保证 'A' + 1 == 'B' 或者 ('a' & 0xDF) == ('A' & 0xDF) - Roland Illig

2
@Eric
“为什么一段可行的代码解决方案会被投票否定?当然,它可能不是最快的方式,而且看起来很丑陋,但比说“strtol”或“sscanf”更具有指导意义。如果你自己尝试一下,你会学到一些关于底层发生的事情。”
“我并不认为你的解决方案应该被投票否决,但我猜测为什么会发生这种情况,因为它不太实用。投票的想法是让“最佳”答案浮现在顶部,而你的答案可能更具指导性,说明了底层发生的事情(或者可能发生的方式),但绝对不是在生产系统中解析十六进制数字的最佳方式。”
“再次强调,从教育角度来看,我认为你的答案没有问题,我肯定不会(也没有)对其进行投票否定。不要因为有些人不喜欢你的答案就感到沮丧,停止发布。这种情况经常发生。”
“我不确定我的回答是否会让你对自己的答案被投票否定感到更好,但我知道当你问为什么被投票否定时没有人回答,这尤其不好玩。”

7
2008年8月,该网站刚刚建立,尚未实施评论功能 - Derek Park

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