printf从char数组中添加额外的`FFFFFF`到hex打印

请考虑以下简化代码。 我想从文件中提取一些二进制数据/流,并以hex格式将其打印到标准输出。

我有额外的3个字节0xFFFFFF 。 怎么了? 多余的字节来自哪里?

产量

 in: 2000FFFFFFAF00690033005A00 out: 2000FFFFFFAF00690033005A00 

program.c

 #include  #include  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:\n\t"); 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:\n\t"); for( i=0; i<10; i++ ) printf("%02X", buf[i]); printf("\n"); fclose(outfile); return 0; } 

签名扩展。 您的编译器将char实现为signed char 。 当你将字符传递给printf它们都会在传播到int期间进行符号扩展。 当第一位为0时,这无关紧要,因为它会延长0秒。

二进制中的1010111110101111由于第一位是1 ,当它传递给printf它会在转换为int时将所有1 s扩展为int使其成为11111111111111111111111110101111 ,即hex值。

解决方案:而是使用unsigned char来防止在调用中出现符号扩展forms

 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个hex数字,因此printf %02可以使用两位数输出。

请注意,有两种C方言:一种是普通char签名,另一种是无符号方言。 在你的签名中。 您可以使用选项更改它,例如gcc和clang support -funsigned-char-fsigned-char

printf()是一个可变参数函数,它的附加参数(对应于其原型的一部分)受默认参数提升的影响 ,因此char被提升为int

由于您的char已经签名为1 ,因此对于0xAF元素,最高有效位设置为1。 在促销期间,传播了比特,导致int类型的0xFFFFFFAF ,在您的实现中可能是sizeof(int) = 4

顺便提一下,您正在调用未定义的行为 ,因为%X格式说明符应该用于unsigned int类型的对象,或者至少用于未设置的MSB的int (这是常见的,广泛接受的做法)。

如建议您可以考虑使用明确的unsigned char类型。


1)实现可以选择char符号和无符号表示。 char被签名是相当普遍的,但你不能认为它是地球上其他所有编译器的理所当然。 他们中的一些人可能允许在这两种模式之间进行选择,如Jens的回答中所述 。