如何在C语言中将十六进制字符串转换为二进制字符串

9

我有一个包含十六进制值的文本文件。现在我需要将这些十六进制值转换为二进制并保存到另一个文件中。 但是我不知道如何将十六进制值转换为二进制字符串! 请帮忙...


你可以像这样尝试一下: http://johnsantic.com/comp/htoi.html - Andrija Sucevic
5
作业?我确定你是指代表而不是价值观:无论它们如何被表示,其价值观都是相同的。 - pmg
1
@pmg:是的,我在做一些类似家庭作业的事情。实际上,我将一个图像文件转换为十六进制文件。现在我需要从那个十六进制文件创建图像。 - Midhun MP
1
@StanlyMoses:在将问题标记为重复之前,请仔细阅读问题,这个问题是关于如何将十六进制转换为二进制,而另一个问题则是关于如何将二进制转换为十六进制。 - Midhun MP
8个回答

11

非常简单,因为翻译是逐位进行的。

0 - 0000
1 - 0001
2 - 0010
3 - 0011
4 - 0100
5 - 0101
6 - 0110
7 - 0111
8 - 1000
9 - 1001
A - 1010
B - 1011
C - 1100
D - 1101
E - 1110
F - 1111

比如说,十六进制数FE2F8在二进制里就是11111110001011111000


5
const char input[] = "..."; // the value to be converted
char res[9]; // the length of the output string has to be n+1 where n is the number of binary digits to show, in this case 8
res[8] = '\0';
int t = 128; // set this to s^(n-1) where n is the number of binary digits to show, in this case 8
int v = strtol(input, 0, 16); // convert the hex value to a number

while(t) // loop till we're done
{
    strcat(res, t < v ? "1" : "0");
    if(t < v)
        v -= t;
    t /= 2;
}
// res now contains the binary representation of the number

作为另一种选择(假设没有像"0x3A"这样的前缀):
const char binary[16][5] = {"0000", "0001", "0010", "0011", "0100", ...};
const char digits = "0123456789abcdef";

const char input[] = "..." // input value
char res[1024];
res[0] = '\0';
int p = 0;

while(input[p])
{
    const char *v = strchr(digits, tolower(input[p++]));
    if (v)
        strcat(res, binary[v - digits]);
}
// res now contains the binary representation of the number

@OuwenHuang,你确定你的“digits”是正确的吗?或者你在哪里遇到了那个错误? - Mario
需要一些帮助。尝试了答案的版本2,并且strcat根本没有填充res数组,它始终保持为\0。 - sce
@sce 很难猜测。只需创建您自己的问题并链接此答案以供参考即可。 - Mario
第二个解决方案具体是如何工作的?v[0]是字符,或者在ASCII值上是数字,因此如果v[0]=f,则对二进制的索引为102,而不是15。至少这似乎是这种情况,因为这行代码在我的程序中会导致段错误。 - krb686
无法让第二个解决方案起作用,但有效的方法是 long v = strtol({input[p++], 0}, NULL, 16); strcat(res, binary[v]); - krb686
显示剩余3条评论

1
有许多方法可以解决这个问题,其中一些使用算术运算将ASCII字符范围0-9和a-f(或A-F)转换为二进制。我想找到一种只使用查找表的解决方案,并将其与使用算术运算的解决方案进行基准测试。令人奇怪的是,以上答案中没有一个实现了纯算术解决方案,一些答案甚至假定“转换为二进制”意味着将其转换为由字符“0”和“1”组成的ASCII字符串。
首先,我们需要进行一些设置。首先,我们希望将整个测试数据存储在内存中,以避免磁盘I/O影响测试。以下是如何创建一个包含104857600字节(大约105 MB)的字符数组“testdata”的头文件。由于问题是如何转换文件,因此我们的实现应该在大型数据上快速运行。
$ { printf "char *testdata =\""; cat /dev/urandom \
    | tr -d -c "0123456789abcdefABCDEF" \
    | dd count=100 iflag=fullblock bs=1M; printf "\";\n" } > testdata.h

接下来,我们创建查找表。我看到两种可能的方法可以用查找表解决这个问题。一种是将单个ASCII十六进制字符映射到半字节,另一种是将两个十六进制字符映射到一个完整字节。在前一种情况下,查找表必须有256个条目。在后一种情况下,查找表必须有256*256=65536个条目。我们可以通过意识到第一个字节的第一位永远不会被使用来减少后者的大小。因此,我们只需要一个包含128*256=32768个条目的查找表。由于该解决方案还需要额外的计算步骤(应用位掩码),因此我们将对两种方法进行基准测试。我们最终得到以下测试用例:
1. 算术解法 2. 256个条目的查找表 3. 32768个条目的查找表 4. 65536个条目的查找表
第一个查找表很容易使用一些Python生成:
#!/usr/bin/env python

import sys,struct

sys.stdout.write("unsigned char base16_decoding_table1[256] = {\n")

for i in xrange(256):
    try:
        j = str(int(chr(i), 16))
    except:
        j = '0'
    sys.stdout.write(j+',')
sys.stdout.write("};\n")

sys.stdout.write("\n")

l = 128*256*["0"]

for a in ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','A','B','C','D','E','F']:
    for b in ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','A','B','C','D','E','F']:
        l[struct.unpack("<H", a+b)[0]] = str(int(a+b, 16))

line = "unsigned char base16_decoding_table2[%d] = {"%(128*256)

for e in l:
    line += e+","
    if len(line) > 70:
        sys.stdout.write(line+"\n")
        line = ""
sys.stdout.write(line+"};\n")

sys.stdout.write("\n")

l = 256*256*["0"]

for a in ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','A','B','C','D','E','F']:
    for b in ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','A','B','C','D','E','F']:
        l[struct.unpack("<H", a+b)[0]] = str(int(a+b, 16))

line = "unsigned char base16_decoding_table3[%d] = {"%(256*256)

for e in l:
    line += e+","
    if len(line) > 70:
        sys.stdout.write(line+"\n")
        line = ""
sys.stdout.write(line+"};\n")

然后:

python gen.py > base16_decoding_table.h

现在我们可以编写一些 C 代码进行测试。

#include <stdio.h>
#include <time.h>
#include <inttypes.h>

#include "testdata.h"
#include "base16_decoding_table.h"

#define TESTDATALEN 104857600

/* the resulting binary string is half the size of the input hex string
 * because every two hex characters map to one byte */
unsigned char result[TESTDATALEN/2];

void test1()
{
    size_t i;
    char cur;
    unsigned char val;
    for (i = 0; i < TESTDATALEN; i++) {
        cur = testdata[i];
        if (cur >= 97) {
            val = cur - 97 + 10;
        } else if (cur >= 65) {
            val = cur - 65 + 10;
        } else {
            val = cur - 48;
        }
        /* even characters are the first half, odd characters the second half
         * of the current output byte */
        if (i%2 == 0) {
            result[i/2] = val << 4;
        } else {
            result[i/2] |= val;
        }
    }
}

void test2()
{
    size_t i;
    char cur;
    unsigned char val;
    for (i = 0; i < TESTDATALEN; i++) {
        cur = testdata[i];
        val = base16_decoding_table1[(int)cur];
        /* even characters are the first half, odd characters the second half
         * of the current output byte */
        if (i%2 == 0) {
            result[i/2] = val << 4;
        } else {
            result[i/2] |= val;
        }
    }
}

void test3()
{
    size_t i;
    uint16_t *cur;
    unsigned char val;
    for (i = 0; i < TESTDATALEN; i+=2) {
        cur = (uint16_t*)(testdata+i);
        // apply bitmask to make sure that the first bit is zero
        val = base16_decoding_table2[*cur & 0x7fff];
        result[i/2] = val;
    }
}

void test4()
{
    size_t i;
    uint16_t *cur;
    unsigned char val;
    for (i = 0; i < TESTDATALEN; i+=2) {
        cur = (uint16_t*)(testdata+i);
        val = base16_decoding_table3[*cur];
        result[i/2] = val;
    }
}

#define NUMTESTS 1000

int main() {
    struct timespec before, after;
    unsigned long long checksum;
    int i;
    double elapsed;

    clock_gettime(CLOCK_MONOTONIC, &before);
    for (i = 0; i < NUMTESTS; i++) {
        test1();
    }
    clock_gettime(CLOCK_MONOTONIC, &after);

    checksum = 0;
    for (i = 0; i < TESTDATALEN/2; i++) {
        checksum += result[i];
    }
    printf("checksum: %llu\n", checksum);
    elapsed = difftime(after.tv_sec, before.tv_sec) + (after.tv_nsec - before.tv_nsec)/1.0e9;
    printf("arithmetic solution took %f seconds\n", elapsed);

    clock_gettime(CLOCK_MONOTONIC, &before);
    for (i = 0; i < NUMTESTS; i++) {
        test2();
    }
    clock_gettime(CLOCK_MONOTONIC, &after);

    checksum = 0;
    for (i = 0; i < TESTDATALEN/2; i++) {
        checksum += result[i];
    }
    printf("checksum: %llu\n", checksum);
    elapsed = difftime(after.tv_sec, before.tv_sec) + (after.tv_nsec - before.tv_nsec)/1.0e9;
    printf("256 entries table took %f seconds\n", elapsed);

    clock_gettime(CLOCK_MONOTONIC, &before);
    for (i = 0; i < NUMTESTS; i++) {
        test3();
    }
    clock_gettime(CLOCK_MONOTONIC, &after);

    checksum = 0;
    for (i = 0; i < TESTDATALEN/2; i++) {
        checksum += result[i];
    }
    printf("checksum: %llu\n", checksum);
    elapsed = difftime(after.tv_sec, before.tv_sec) + (after.tv_nsec - before.tv_nsec)/1.0e9;
    printf("32768 entries table took %f seconds\n", elapsed);

    clock_gettime(CLOCK_MONOTONIC, &before);
    for (i = 0; i < NUMTESTS; i++) {
        test4();
    }
    clock_gettime(CLOCK_MONOTONIC, &after);

    checksum = 0;
    for (i = 0; i < TESTDATALEN/2; i++) {
        checksum += result[i];
    }
    printf("checksum: %llu\n", checksum);
    elapsed = difftime(after.tv_sec, before.tv_sec) + (after.tv_nsec - before.tv_nsec)/1.0e9;
    printf("65536 entries table took %f seconds\n", elapsed);

    return 0;
}

让我们编译这个东西:
$ gcc -O3 -g -Wall -Wextra test.c

并运行它:

$ ./a.out

结果:
  1. 算术解决方案:437.17秒
  2. 256个条目的查找表:117.80秒
  3. 32768个条目的查找表:52.33秒
  4. 65536个条目的查找表:44.66秒
我们可以得出结论,无论何时,查找表都比算术解决方案更好,并且为更大的查找表浪费内存可能值得额外的运行时间。

更多的条目如何可能具有更少的基准测试时间? - Zimano
@Zimano 因为更大的查找表意味着需要进行更多的查找操作,而减少算术运算。 - josch
哦,我明白了。实际上这很有趣,谢谢! - Zimano

1
void hex_binary(char * res){
char binary[16][5] = {"0000", "0001", "0010", "0011", "0100", "0101","0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110","1111"};
char digits [] = "0123456789abcdef";

const char input[] = "a9e6"; // input value
res[0] = '\0';
int p = 0;
int value =0;
    while(input[p])
    {
        const char *v = strchr(digits, tolower(input[p]));
        if(v[0]>96){
            value=v[0]-87;
        }
        else{
            value=v[0]-48;
        }
        if (v){
            strcat(res, binary[value]);
        }
        p++;
    }
    printf("Res:%s\n", res);
}

答案1的编译版本 - Will

-1
void printBin(unsigned int num){
  char str[sizeof(num)*8];
  char *p = str;
  for(*p='0'; num; num/=2) { *p++='0'+num%2; } //store remainders
  for(--p; p>=str; putchar(*p--)) {;}          //print remainders in reverse
  putchar('\n');
}

-2
最快最简单的方法是读取十六进制文件,在读取每个字符('0'到'F')时,查找相应的二进制值(0到15)。当然,还有更优雅的方法,但这非常直接,可能像这样:
switch (charval) {
  case '0': binval = 0;
  case '1': binval = 1;
  case '2': binval = 2;
  case '3': binval = 3;
   ....
  case 'a': binval = 10;
  case 'b': binval = 11;
  case 'A': binval = 10;
  case 'B': binval = 11;
  ....
  case 'f':  binval = 15;
  case 'F':  binval = 15;
  default:   binval = -1;  // error case
}

现在,您需要使用移位和IORs / ADDs从这些各自的4位二进制值构建所需大小的单词。

这样你将获得十六进制数字的数值,但你不会得到二进制表示。另外,要进行这样的查找,我要么使用一个固定的字符串(0123456789abcdef),然后通过在其上使用strchr()来获取数字的值,要么我只需使用一个char [256]数组,并将字符/数字作为索引。这样你既可以使用二进制表示作为值,也可以跳过额外的转换。 - Mario
1
这不是表查找。表查找的时间复杂度为O(1),但您的解决方案使用的查找时间复杂度为O(N)。 - josch

-3

这是我用来将十六进制转换为二进制的函数,逐字节进行转换。

void HexToBin(char hex_number, char* bit_number) {
    int max = 128;
    for(int i = 7 ; i >-1 ; i--){
        bit_number [i] = (hex_number & max ) ? 1 : 0;
        max >>=1;
    }
}

以及对函数的调用:

void main (void){

    char hex_number = 0x6E; //0110 1110
    char bit_number[8]={0,0,0,0,0,0,0,0};
    HexToBin(hex_number,bit_number);

    for(int i = 7 ; i >-1 ; i--)
        printf("%d",bit_number[i]);

    printf("\n");
    system("pause");
}

这里是MSDOS的答案:

01101110

Press a key to continue . . .

非常简单!


这会以 ASCII 形式打印出 0 和 1。它是如何将任何十六进制数转换为它们的二进制表示呢? - josch

-3
#include <stdio.h>

int main()
{
    long int binaryNumber,
             hexadecimalNumber = 0,
             j = 1,
             remainder;

    printf("Enter any number any binary number: ");
    scanf("%ld", &binaryNumber);

    while(binaryNumber != 0) {
        remainder = binaryNumber % 10;
        hexadecimalNumber = hexadecimalNumber + remainder * j;
        j = j * 2;
        binaryNumber = binaryNumber / 10;
    }
    printf("Equivalent hexadecimal value: %X", hexadecimalNumber);
    return 0;
}

虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - JAL

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