代码编译运行环境:Windows 64bits+VS2017+Debug+Win32
在编程或者面试过程中,可能会遇到如下问题:
char c=128;
printf("%d",c); //输出-128
为什么一个正整数128以整型int输出却变成了一个负数?
在理解上面的问题时,我们需要先了解如下问题。
(1)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的补码:10000000b。注意对于计算机来说,整型数值存储的都是补码,而反码、源码是为了方便编程人员理解数据的变换而提出来的。
(2)当char转换为int时,内存中的数据如何从1个字节扩展到4个字节?这个是本文的核心问题,理解了这个,就可以很好地解释为什么char c=128;printf(“%d”,c); 输出的是-128。
当char型扩展到int型时,C标准中有如下规则: (2.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.2)长数据类型缩减为短数据类型 如果长数据类型的高字节全为1或全为0,则会直接截取低字节赋给短数据类型;如果长数据类型的高字节不全为1或不全为0,则转换就会发生错误。
(2.3)同一长度的数据类型中有符号数与无符号数的相互转化 直接将内存中的数据赋给要转化的类型,数值大小则会发生变化,因为以不同类型解释同一段内存数据会得到不同的数值。比如一个字节中存放的数据是11111111,以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来解释的话,就是232−1−127=4294967168232−1−127=42949671682^{32}-1-127=4294967168。
根据以上分析,我们可以清楚准确的推断出下面的输出。
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