以(unsigned)char为例笔记。数据的溢出实际上是补码运算最高位接受进位或产生进位造成的,可见笔记:C 位运算符操作的二进制码(补码)。
1:C语言数据类型
C标准所规定的各数据类型所定义变量会占用的内存大小:
在不同的平台上C的每一种数据类型变量所占用的内存大小可能会不同。一般在32位系统之上,char变量占用一个字节内存,short变量占用2个字节内存,int变量占用4个字节内存,float变量占用4个字节内存,double变量占用8个字节内存,long变量占用4个字节内存。每个字节为8位。测试这些数据类型变量占用内存大小时可用sizeof测试一下。
针对有符号类型变量。
如char 类型变量占用8个位,可表示的数据范围-2^7 ~ 2^7 – 1。为什么呢?
如当前数据类型变量占用n位,则在此n位中存最小的正数的补码应该为:
0 | 1 | ...... | 1 | 1 |
符号位 数据最高位 1bit 0bit
则n位所存的最大正数数为:2^0 + 2^1 + …+2^(n-2) = 1*( 1 – 2^[n-1]) / (1 – 2) = 2^[n-1] – 1。
即补码转换成原码所对应的最大值是- (2^[n-1] – 1)。可根据实际的编程经验可知,最小的负数可以达到-2^[n-1]。
以下依据解决这个问题:
无符号数就是没有符号,全为正数。所占n位内存的每一位都用来表示数值,则此无符号数的数值范围为:0 ~ 2^n - 1。
如unsigned char ch变量能表示的最大数值为2^8 – 1= 255,最小数值是0,那么当给ch赋255以上值或赋0以下值时ch对应的值是多少呢。
255的补码形式是8个位之上全是1,当255加1成256时,最高位有进位,数据溢出。8位全变为0,ch此时的输出值为0。如此,257对应的ch输出值为1,……,256 + 255 = 511时ch值为255。n * 2^8 ~ (n + 1) 2^8赋值与ch时,ch的有效值为0 ~ 255。[n = 0, 1, 2…,不宜过大]。其中8可以为,16,32,64。
可用程序代表性的验证一下:
int main(void) { unsigned char c1, c2, c3, c4; c1 = 256; c2 = 257; c3 = 511; c4 = 256 * 10; printf("%d, %d, %d, %d\n", c1, c2, c3, c4); return 0; }
编译并运行程序的结果如下:
misskissc@DeskTop:~/C/Fundation$ gcc datatype.c datatype.c: In function ‘main’: datatype.c:8: warning: large integer implicitly truncated to unsigned type datatype.c:9: warning: large integer implicitly truncated to unsigned type datatype.c:10: warning: large integer implicitly truncated to unsigned type datatype.c:11: warning: large integer implicitly truncated to unsigned type misskissc@DeskTop:~/C/Fundation$ ./a.out 0, 1, 255, 0 |
与无符号变量不同的是,最高位已经变成符号位。
如char ch变量的数值范围为[-128,127],当给ch赋-128以下的值或赋127以上的值时ch对应的实际值是多少呢。
上限溢出
[1] 128为正数,将128以补码的形式存为:10000000。
[2] 对于char类型变量来说,这个二进制是char类型变量的补码。这个数值就是补码-0的替身-128。
[3]129存于内存的代码为:10000001。
[4] 对于char类型变量来说,这个二进制是char类型变量的补码。对应的原码为1111 1111,即ch的值为-127。
[5]……
[6]反过来推,当补码为11111111时,对应的原码为1000 0001即ch的值为-1,此时对应的实际的数为127 + 128 = 255。
[7]当255再增1到256时,256对应补码为0000 0000,ch对应的值再次为0。
[8]0 ~ 127是ch的有效值。
当给ch赋值为n * 2^(8-1) ~ (n +1) * 2^(8-1) – 1值时, ch对应的值为-127 ~ -1。[n= 1, 3, 5, 7,…不宜过大]。给ch赋值你n * 2^(8-1)时ch为0。其中指数值8可以为,16,32,64。
当给ch赋值为n * 2^(8-1) ~ (n +1) * 2^(8-1) – 1值时,ch对应值为 0 ~ 127,[n= 2, 4, 6,8…不宜过大]。给ch赋值你n * 2^(8-1)时ch为0。其中指数值8可以为,16,32,64。
程序代表验证:
int main(void) { char c1, c2, c3, c4, c5, c6; c1 = 128; c2 = 129; c3 = 255; c4 = 128 * 10; c5 = 128 * 3 + 1; c6 = 128 * 4 + 1; printf("%d, %d, %d, %d, %d, %d\n", c1, c2, c3, c4, c5, c6); return 0; }
编译并执行程序的结果:
misskissc@DeskTop:~/C/Fundation$ gcc datatype.c datatype.c: In function ‘main’: datatype.c:11: warning: overflow in implicit constant conversion datatype.c:12: warning: overflow in implicit constant conversion datatype.c:13: warning: overflow in implicit constant conversion misskissc@DeskTop:~/C/Fundation$ ./a.out -128, -127, -1, 0, -127, 1 misskissc@DeskTop:~/C/Fundation$ |
下限溢出
[1] -129为负数,存于内存中的补码形式为:01111111
得-129补码过程:存有符号数129需要9位,增添一位符号位表示129原码:0 1000 0001,-129的补码为129原码取反加1,即为1 0111 1111,由于char类型变量只有故舍弃最高位得-129在char类型变量中的补码为:0111 1111。
[2] 0111 1111补码表示值为正,即为127。
[3]推测ch输出值为1时的溢出值为 – 129 –(127-1) = -255。
[4]当给ch赋值-256时,ch的输出值为0。
[5]-128 ~ 0是ch的有效值。
当给ch赋值为 - ( (n +1)* 2^(8-1) – 1 ) ~ - n * 2^(8-1)值时,[ n=1, 3, 5, 7,…,不宜过大],ch输出有效值为127 ~ 1。赋n * 2^8时ch的输出值为0。其中指数值8可以为,16,32,64。
当给ch赋值- ( (n + 1)* 2^(8-1) – 1 ) ~ - n * 2^(8-1)时,ch对应的值为-128 ~ -1,[ n=0, 2, 4, 6…,不宜过大]。其中指数值8可以为,16,32,64。
程序代表验证:
int main(void) { char c1, c2, c3, c4, c5; c1 = -129; c2 = -255; c3 = -256; c4 = -128 * 3- 1; c5 = -128 * 4 - 1; printf("%d, %d, %d, %d, %d\n", c1, c2, c3, c4, c5); return 0; }
程序编译及执行结果为:
misskissc@DeskTop:~/C/Fundation$ gcc datatype.c datatype.c: In function ‘main’: datatype.c:8: warning: overflow in implicit constant conversion datatype.c:9: warning: overflow in implicit constant conversion datatype.c:10: warning: overflow in implicit constant conversion datatype.c:11: warning: overflow in implicit constant conversion datatype.c:12: warning: overflow in implicit constant conversion misskissc@DeskTop:~/C/Fundation$ ./a.out 127, 1, 0, 127, -1 misskissc@DeskTop:~/C/Fundation$ |
[1]将溢出值以补码的形式存于内存中。
[2]根据数据的类型舍多余位[高位]得到此数据类型下的补码值。
[3]将补码专为原码计算输出值。
[1]无符号数的溢出规律是重复型的:始终都是无符号数的数据范围内。无符号数的下限溢出分析都是一样的。
[2]有符号的溢出规律是交叉型的:上限溢出到下限值,再溢出后回到有效范围内,如此重复。下限溢出后到上限值,再溢出后回到有效范围内,如此重复。
[3]分析溢出还是靠“判断变量值溢出时的实际输出值步骤”来分析。
C Note Over。