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
秒。
二进制中的10101111
是10101111
由于第一位是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的回答中所述 。