关于 %x 打印 char,却占 4 字节的思考

问题起源

#include <stdio.h>

int main() {
    char x[] = "你好世界,hello world!";
    printf("%s\n", x);
    printf("0x%x\n", x[0]);
    printf("%d\n", sizeof(x));
    printf("%d\n", sizeof(x[0]));

}

/*输出
 *----------------------
 *你好世界,hello world!
 *0xffffffe4
 *28
 *1
 *----------------------
 */

当使用以上代码打印中文字符串的编码时,发现其打印的一个 char 字符竟然占 4 字节 与 char 占 1 字节 不符。

原因分析

%x 的含义是打印 16 进制形式的无符号整形(即 unsigned int,占 4 字节),所以上述程序在打印 x[0] 时其实是做了隐式的 charusinged int 的转换的,而在 windows 中 char 实际上是 signed char 1,故可将变量提升过程理解为 signed charsigned intunsigned int

在上述程序中 x[0] 的实际值为 0xe4(utf-8 编码),其符号位为 1,当其转换为 signed int 时会存在符号位的填充,即前三个字节全填充 1(这与补码的性质有关),故变成了 0xffffffe4,而signed intunsigned int 不会对储存的二进制做任何改变,故最终打印结果为 0xffffffe4

程序改进

根据上述分析,我们只需将 char 改为 unsigned char,此时由于类型提升途径为 unsigned charusigned int ,不存在符号位的填充,其前三个字节全部填充 0,最后打印出来的依旧是 0xe4。程序代码如下:

#include <stdio.h>

int main() {
    unsigned char x[] = "你好世界,hello world!";
    printf("%s\n", x);
    printf("0x%x\n", x[0]);
    printf("%d\n", sizeof(x));
    printf("%d\n", sizeof(x[0]));

}

/*输出
 *----------------------
 *你好世界,hello world!
 *0xe4
 *28
 *1
 *----------------------
 */

总结

实际上只要使用 %x 打印,其本质上都是打印的 4 字节的无符号整形类别,如果需要使用其来打印字符串的编码,则需要将字符串定义为 unsigned char [] 类型,以避免当编码最高位为 1 时,被错当为符号位,在类型提升中填充符号位,从而将前 3 个字节也打印出来。


  1. 这一说法并不严谨,c标准中并未指定 char 是有符号还是无符号,它只是规定了其行为要么与 signed char 一致要么与 unsigned char 一致。 ↩︎


版权声明:本文为qq_18762155原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。