代码编译运行环境:Windows 64bits+VS2017+Debug+Win32
1.问题描述
在编程或者面试过程中,关于数据类型宽度的扩展,可能会遇到如下问题:
char c=128;
printf("%d",c); //输出-128
为什么一个正整数 128 以整型 int 输出时变成了一个负数?
2.问题分析
在理解上面的问题时,我们需要先了解如下问题。
char 型所能表示的数据范围是 -128~127。当把 128 赋值给 char 型变量时,那么内存中实际存储的是什么呢?将以上面的代码在 Debug 模式下转到反汇编,汇编代码如下:
char c=128;
00B16AB0 mov byte ptr [c],80h
printf("%d",c);
00B16AB4 movsx eax,byte ptr [c]
00B16AB8 mov esi,esp
00B16ABA push eax
00B16ABB push 0B1EC90h
00B16AC0 call dword ptr ds:[0B2240Ch]
00B16AC6 add esp,8
00B16AC9 cmp esi,esp
00B16ACB call __RTC_CheckEsp (0B113CFh)
从汇编代码可以看出,char 型变量 c 中存储的是 128 的补码,也是 128的原码:10000000b。注意对于计算机来说,整型数值存储的都是补码,而反码、原码是为了方便编程人员理解数据的变换而提出来的。
当 char 转换为 int 时,内存中的数据如何从 1 个字节扩展到 4 个字节?这个是本文的核心问题,理解了这个,就可以很好地解释为什么char c=128;printf("%d",c)
输出的是 -128。
当 char 型扩展到 int 型时,C 标准中有如下规则:
(1)短数据类型扩展为长数据类型。
(a)要扩展的短数据类型为有符号数,进行符号扩展,即短数据类型的符号位填充到长数据类型的高字节位(即比短数据类型多出的那一部分),保证扩展后的数值大小不变
char x=10001001b; short y=x; 则y的值应为11111111 10001001b; //例1
char x=00001001b; short y=x; 则y的值应为00000000 00001001b; //例2
(b)要扩展的短数据类型为无符号数,进行零扩展,即用零来填充长数据类型的高字节位。
unsigned char x=10001001b; short y=x; 则y的值应为00000000 10001001b; //例1
unsigned char x=00001001b; short y=x; 则y的值应为00000000 00001001b; //例2
(2)长数据类型缩减为短数据类型。
如果长数据类型的高字节全为 1 或全为 0,则会直接截取低字节赋给短数据类型;如果长数据类型的高字节不全为 1 或不全为 0,则转换就会发生错误。
(3)同一长度的数据类型中有符号数与无符号数的相互转化。
直接将内存中的数据赋给要转化的类型,数值大小则会发生变化,因为以不同类型解释同一段内存数据会得到不同的数值。比如一个字节中存放的数据是 1111 1111b,以 unsigned char 来解释是 255,以 char 来解释是 -1。
根据以上规则,可以得出当 char c 是一个有符号的字符变量,其内存中存储的是 1000 0000,但当它被传送到 printf() 函数的参数时,是将 c 按照 int 来进行宽度扩展后再传给 printf()。
128 的补码是 1000 0000b,16 进制是 0x80,当它扩展为 int 时,由于 int 是 4 个字节,需要进行短数据类型扩展到长数据类型。由于内存中存放的是 10000000,以 char 型来解释的话第一位为符号位,表示负数,进行符号扩展为 int 后,int 型变量中存储的数据是:11111111 11111111 11111111 1000000,即 0xffffff80。以 int 来解释的这四个字节的数据,其值就是 -128,以 unsigned int 来解释的话,就是 2 32 − 1 − 127 = 4294967168 2^{32}-1-127=4294967168 232−1−127=4294967168。
3.代码验证
根据以上分析,我们可以清楚准确地推断出下面的输出。
unsigned char uc=128;
char c=128;
printf("%d\n",uc); //128
printf("%d\n",c); //-128
printf("%u\n",uc); //128
printf("%u\n",c); //4294967168
printf("%08x\n",uc); //0x00000080
printf("%x\n",c); //0xffffff80
应该不会为这些输出结果而感到惊讶和困惑了吧!
参考文献
[1] 类型扩展
[2] char c=128