数据溢出
C语言中的基本数据类型占据不同的内存空间,都有一定的取值范围,如果在定义变量时,将一个超出取值范围的值赋给了变量,就会发生数据溢出。例如,字符类型变量占据的内存大小为1个字节(8bit),取值范围为-128~127,如果为字符类型变量所赋的值超出这个范围,编译器就不能正确解读这个数据,示例代码如下所示:
char ch1 = -129; //超出下限范围,ch1的结果为127
char ch2 = -128; //ch2结果为-128
char ch3 = 0; //ch3结果为0
char ch4 = 127; //ch4结果127
char ch5 = 128; //超出上限范围,ch5结果为-128
在上述代码中,ch1与ch5在赋值时都超出了字符类型的取值范围,发生数据溢出,因此不能正确的显示结果。在赋值时,数据如果小于取值范围的最小值称为数据下溢,数据如果大于取值范围的最大值称为数据上溢。
如果读者继续在字符范围之外逐渐递增(递减)取值,则会发现一个有趣的现象,字符数据类型的取值以255(127-(-128)=255)为周期循环回绕读取数据。例如,上述代码中,为ch5赋值128,则真实读取的数据为-128,如果为ch5赋值129,则真实读取的数据为-127;如果为ch5赋值为130,则真实读取的数据为-126…,这样当为ch5赋值为383时,真实读取的数据为127,为ch5赋值为384时,真实读取数据又变回-128。该过程的示例代码如下:
ch5 = 128; //真实读取数据为-128
ch5 = 129; //真实读取数据为-127
ch5 = 130; //真实读取数据为-126
…
ch5 = 383; //真实读取数据为127
ch5 = 384; //真实读取数据为-128
为ch5赋值128时,其值为-128,为ch5赋值383时,其值为127。383-128=255,这正好是字符类型取值范围的周期。如果继续递增赋值,则ch5的取值又回绕进入一个新的循环,该溢出过程类似时钟的循环,如图1所示。
图1 字符数据类型数据溢出的循环回绕现象
同理,整型类型的数据也会发生溢出,而且在C语言编程中,整数数据溢出最为常见。整型类型可以分为有符号整型和无符号整型,无论是哪种整型类型,当赋值超出取值范围时都会发生回绕现象。例如,对于unsigned int类型,其取值范围0~4294967295 (0 ~ 232-1),当为unsigned int类型的变量赋值时,如果所赋值超出这个范围也会发生回绕,示例代码如下:
unsigned int num1 = -1; //数据下溢,真实读取数据为4294967295
unsigned int num2 = 0; //真实读取数据为0
unsigned int num3 = 4294967295; //真实读取数据为4294967295
unsigned int num4 = 4294967296; //数据上溢,真实读取数据为0
需要注意的是,unsigned int类型变量在输出时以u%格式输出,输出格式将在2.4.1节讲解。
在实际编程中,想要记住不同数据类型的取值范围是很难的,为了减少编程中的数据溢出错误,C语言针对不同的整型类型都定义了一个宏,用于表示该数据类型的极值,具体如表1所示。
表1 不同整型数据类型极值宏定义
数据类型 | 极大值 | 极小值 |
---|---|---|
short | SHRT_MAX | SHRT_MIN |
int | INT_MAX | INT_MIN |
long | LONG_MAX | LONG_MIN |
unsigned short | USHRT_MAX | - |
unsigned int | UINT_MAX | - |
unsigned long | ULONG_MAX | - |
表1中的极值宏定义包含在limits.h标准库中,使用这些宏定义需要包含该标准库。