考虑下面的简化代码。我想从文件中提取一些二进制数据/流,并将其打印为十六进制格式的标准输出。
我得到了额外的3字节0xFFFFFF
。怎么了?额外的字节是从哪里来的?
输出
in:
2000FFFFFFAF00690033005A00
out:
2000FFFFFFAF00690033005A00
程序.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
int i;
char raw[10] = {0x20,0x00,0xAF,0x00,0x69,0x00,0x33,0x00,0x5A,0x00};
FILE *outfile;
char *buf;
printf("in:nt");
for( i=0; i<10; i++ )
printf("%02X", raw[i]);
outfile = fopen("raw_data.bin", "w+b");
fwrite(raw, 1, 10, outfile);
buf = (char *) malloc (32 * sizeof(char));
fseek(outfile, 0, SEEK_SET);
fread(buf, 1, 10, outfile);
printf("nout:nt");
for( i=0; i<10; i++ )
printf("%02X", buf[i]);
printf("n");
fclose(outfile);
return 0;
}
符号扩展。您的编译器正在将char
实现为signed char
。当您将字符传递给printf
时,它们在升级到int
s期间都被符号扩展。当第一位是0时,这无关紧要,因为它会被0
s扩展。
二进制中的0xAF
是10101111
。由于第一个位是1
,当将其传递给printf
时,它在转换为int
时被扩展为所有1
,使其成为11111111111111111111111110101111
,即0xFFFFFFAF
,即您所拥有的十六进制值。
解决方案:使用unsigned char
(而不是char
)来防止在调用中发生符号扩展
const unsigned char raw[] = {0x20,0x00,0xAF,0x00,0x69,0x00,0x33,0x00,0x5A,0x00};
原始示例中的所有这些值都是符号扩展的,只是0xAF
是唯一一个在第一位中有1
的值。
相同行为的另一个更简单的例子(实时链接):
signed char c = 0xAF; // probably gives an overflow warning
int i = c; // extra 24 bits are all 1
assert( i == 0xFFFFFFAF );
这是因为从有符号字符转换为有符号整数时,0xAF是负的(它是符号扩展的),而%02X
格式适用于无符号参数,并将转换后的值打印为FFFFFFAF
。
出现额外的字符是因为printf %x
将从不静默地截断值的数字。非负的值也可以进行符号扩展,但这只是添加零位,并且值适合2个十六进制数字,因此printf %02
可以使用两位数输出。
注意有两种C方言:一种是普通char
有符号的,另一种是无符号的。在你的信里有签名。您可以使用一个选项来更改它,例如gcc和clang支持-funsigned-char
和-fsigned-char
。
printf()
是一个可变函数,其附加参数(对应于其原型的...
部分)受到默认参数提升的约束,因此char
被提升为int
。
由于您的char
已签署1,所以对于0xAF
元素,2的补码表示最高有效位被设置为1。在升级期间,已签名的位被传播,结果是int
类型的0xFFFFFFAF
,在您的实现中可能是sizeof(int) = 4
。
顺便说一下,您正在调用未定义的行为,因为%X
格式说明符应该用于类型为unsigned int
的对象,或者至少用于MSB未设置的int
(这是常见的、广泛接受的做法)。
根据建议,您可以考虑使用明确的unsigned char
类型。
1)实现可以在char
的有符号和无符号表示之间进行选择。char
被签名是很常见的,但对于这个星球上的其他编译器来说,你不能认为这是理所当然的。正如Jens的回答中所提到的,其中一些模式可能允许在这两种模式之间进行选择