问题起源
#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] 时其实是做了隐式的 char 到 usinged int 的转换的,而在 windows 中 char 实际上是 signed char 1,故可将变量提升过程理解为 signed char 到 signed int 到 unsigned int。
在上述程序中 x[0] 的实际值为 0xe4(utf-8 编码),其符号位为 1,当其转换为 signed int 时会存在符号位的填充,即前三个字节全填充 1(这与补码的性质有关),故变成了 0xffffffe4,而signed int 到 unsigned int 不会对储存的二进制做任何改变,故最终打印结果为 0xffffffe4。
程序改进
根据上述分析,我们只需将 char 改为 unsigned char,此时由于类型提升途径为 unsigned char 到 usigned 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 个字节也打印出来。
这一说法并不严谨,c标准中并未指定
char是有符号还是无符号,它只是规定了其行为要么与signed char一致要么与unsigned char一致。 ↩︎