在C语言中打印十六进制字符

131
我正在尝试读入一行字符,然后打印出字符的十六进制等效形式。
例如,如果我有一个字符串是"0xc0 0xc0 abc123",其中前两个字符是十六进制中的c0,其余字符是ASCII中的abc123,那么我应该得到
c0 c0 61 62 63 31 32 33

然而,使用%xprintf给了我一个

ffffffc0 ffffffc0 61 62 63 31 32 33

我如何在输出中去除"ffffff"?为什么只有c0(和80)具有ffffff,而其他字符没有?


匹配您的字节数组的字符串将是... "\xc0\xc0abc123" - burito
8个回答

169
您看到的 ffffff 是因为您的系统上 char 是带符号的。在 C 语言中,像 printf 这样的变参函数会将所有小于 int 的整数提升为 int。由于 char 是一个整数(在您的情况下是一个 8 位有符号整数),因此通过符号扩展将您的字符提升为 int
由于 c080 有一个前导的 1 位(并作为 8 位整数为负数),它们被符号扩展了,而样本中的其他字符没有被扩展。
char    int
c0 -> ffffffc0
80 -> ffffff80
61 -> 00000061

这是一个解决方案:

char ch = 0xC0;
printf("%x", ch & 0xff);

这将屏蔽掉上位比特并仅保留您想要的低8位。


18
在 x86-64 的 gcc4.6 中,我的解决方案使用 unsigned char 强制转换比原来节省了一条指令。 - lvella
2
也许我可以帮忙。从技术上讲,这是未定义的行为,因为说明符 x 需要无符号类型,但 ch 被提升为 int。正确的代码应该只需将 ch 强制转换为无符号类型,或使用强制转换为无符号字符和说明符:hhx - 2501
1
如果我有 printf("%x", 0),什么也不会被打印出来。 - Gustavo Meira
它没有打印任何东西,因为最小值设置为0。要解决这个问题,请尝试使用printf("%.2x", 0);,它将增加绘制的最小字符数到2。要设置最大值,请在.前面添加一个数字。例如,您可以通过执行printf("%2.2x", 0);来强制绘制仅有2个字符。 - user2262111
有什么理由认为 printf("%x", ch & 0xff) 比只使用 printf("%02hhX", a) 更好,就像 @brutal_lobster 的 答案 中所述的那样? - maxschlepzig

75

实际上,可以将类型转换为int。 此外,您还可以使用%hhx限定符强制类型转换为char。

printf("%hhX", a);
在大多数情况下,您还需要设置最小长度以填充第二个字符为零:
printf("%02hhX", a);

ISO/IEC 9899:201x规定:

7 长度修饰符及其含义为: hh指示后面的d、i、o、u、x或X转换说明符适用于signed char或unsigned char参数(根据整数提升,参数将被提升,但在打印之前其值必须转换为signed char或unsigned char);或者指示后续


30

你可以创建一个无符号字符:

unsigned char c = 0xc5;

打印它将会给出C5而不是ffffffc5

只有大于127的字符会以ffffff打印,因为它们是负数(char是带符号的)。

或者您可以在打印时转换char

char c = 0xc5; 
printf("%x", (unsigned char)c);

3
+1表示最佳答案,显式类型声明应尽可能接近数据声明(但不要过于接近)。 - Bob Stein

18

您可以使用hh告诉printf参数为无符号字符。使用0进行零填充,2设置宽度为2。xX表示小写/大写十六进制字符。

uint8_t a = 0x0a;
printf("%02hhX", a); // Prints "0A"
printf("0x%02hhx", a); // Prints "0x0a"

编辑:如果读者对2501的说法有所担忧,认为这不是“正确”的格式说明符,我建议他们再次阅读printf链接。特别是:

尽管%c期望int参数,但由于可变函数调用时发生的整数提升,传递char是安全的。

固定宽度字符类型(int8_t等)的正确转换规范在头文件<cinttypes>(C++)或<inttypes.h>(C)中定义(虽然PRIdMAX、PRIuMAX等与%jd、%ju等同)

至于他关于带符号与无符号的观点,在这种情况下并不重要,因为这些值必须始终为正,并且很容易适合有符号int。反正也没有带符号的十六进制格式说明符。

编辑2:(“何时承认错误”版):

如果您读实际的C11标准第311页(PDF的第329页),您会发现:

hh:指定后面的diouxX转换规范适用于signed charunsigned char参数(根据整数提升,参数将被升级,但其值在打印之前应转换为signed charunsigned char);或者后面的n转换规范适用于指向signed char参数的指针。


类型 uint8_t 的说明符不正确。固定宽度类型使用特殊的打印说明符。请参阅:inttypes.h - 2501
是的,但所有的可变参数整数都会被隐式提升为int。 - Timmmm
可能是这样,但就C语言的定义而言,如果您不使用正确的说明符,行为将是未定义的。 - 2501
但是 %x 是正确的说明符。(charunsigned char 会被提升为 int) [http://en.cppreference.com/w/cpp/language/variadic_arguments]。只有在你的平台 int 不适用于某些情况时,才需要使用 PRI 说明符 - 例如 unsigned int - Timmmm
就像我之前已经写过的那样,你假设uint8_t总是被定义为无符号字符。但实际情况并非如此,因为C语言并不作出这样的保证。 - 2501
显示剩余3条评论

14
你可能在一个 char 变量中存储了值 0xc0,它可能是一个带符号的类型,而你的值是负数(最高位为1)。当打印时,它将被转换为 int,为保持语义等效性,编译器会用 0xff 填充额外的字节,使得负的 int 具有与你的负的 char 相同的数值。要解决这个问题,只需在打印时将其强制转换为 unsigned char
printf("%x", (unsigned char)variable);

2

您可能正在从signed char数组中打印内容。请改为从unsigned char数组中打印或使用掩码0xff:例如ar[i] & 0xFF。由于高(符号)位被设置,c0值被扩展为符号位。


0
尝试像这样做:

int main()
{
    printf("%x %x %x %x %x %x %x %x\n",
        0xC0, 0xC0, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33);
}

这将产生以下结果:

$ ./foo 
c0 c0 61 62 63 31 32 33

0
这是一个程序,用于帮助说明符号扩展。请注意,十六进制中的0-127(0到0111 1111)显示如预期,因为符号位为0,所以从8位扩展到32位时,它会用0进行扩展(在十六进制中显示为空白)。一旦达到128(1000 000)有符号字符,它就变成了负数(-128),并且使用1 / F进行符号扩展。
                 unsigned    signed        hex    binary
-----------------------------------------------------------
unsigned char:        127       127         7f    0111 1111
  signed char:        127       127         7f    0111 1111

                 unsigned    signed         hex   binary
---------------------------------------------------------------
unsigned char:        128        128         80   00000000 00000000 00000000 10000000
  signed char:        ...       -128   ffffff80   11111111 11111111 11111111 10000000

程序:

#include <stdio.h>

void print(char c) {
    unsigned char uc = c;
    printf("               %15s %15s %15s\n", "unsigned", "signed", "hex");
    printf("---------------------------------------------------------------\n");
    printf("unsigned char: %15u %15i %15x\n", uc, uc, uc);
    printf("  signed char: %15u %15i %15x\n\n", c, c, c);
}

void main() {
    print(127);
    print(128);
}

即使超过127,无符号字符仍会扩展为0,因为您明确告诉它它是一个正数。

打印有符号字符作为有符号整数时,可以看到符号扩展如何保留-128的值。

(编辑:在示例输出中添加了二进制列,稍后将在程序代码中包括此列。)


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