c-printf将额外的“FFFFFF”添加到字符数组的十六进制打印中



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

我得到了额外的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扩展。

二进制中的0xAF10101111。由于第一个位是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的回答中所提到的,其中一些模式可能允许在这两种模式之间进行选择

最新更新