0%

C语言整型类型长度与类型溢出

题外话

C语言由Dennis M. Ritchie在1973年设计和实现。从那以后使用者逐渐增加。到1978年Ritchie和Bell实验室的另一位程序专家Kernighan合写了著名的《The C Programming Language》,将C语言推向全世界,许多国家都出了译本,国内有一些C语言书就是这本书的翻译或者编译。由这本书定义的C语言后来被人们称作K&R C。

随着C语言使用得越来越广泛,出现了许多新问题,人们日益强烈地要求对C语言进行标准化。这个标准化的工作在美国国家标准局(ANSI)的框架中进行(1983-1988),最终结果是1988年10月颁布的ANSI标准X3.159-1989,也就是后来人们所说的ANSIC标准。由这个标准定义的C语言被称作ANSI C。

ANSI C标准很快被采纳为国际标准和各国的标准。国际标准为ISO/IEC 9899-1990,中国国家标准GB/T15272-94是国际ISO标准的中文翻译。

ANSI C标准化工作的一个主要目标是清除原来C语言中的不安全、不合理、不精确、不完善的东西。由此也产生了ANSIC与K&R C之间的差异。从总体上看,这些差异反应的是C语言走向完善、走向成熟。

我们一般采用ANSI C就可以了。

整型

整型包括:字符,短整型,整型,长整型。每种类型又分为有符号和无符号。

K&R C并没有规定长整型必须比短整型长,只是规定它不得比短整型短。

1
长整型 >= 整型 >= 短整型

ANSI C在此基础上加入了一个规范,说明了各种整型值的最小允许范围。这对程序的可移植性起到了巨大的作用。

变量的最小范围:

类型 最小范围
char 0~127
signed char -127~127
unsigned char 0~255
short int -(2^15 - 1) ~ (2^15 - 1) 即 -32767~32767
unsigned short int 0~(2^16 - 1) 即 0~65535
int -(2^15 - 1) ~ (2^15 - 1) 即 -32767~32767
unsigned int 0~(2^16 - 1) 即 0~65535
long int -(2^31 - 1) ~ (2^31 - 1) 即 -2147483647~2147483647
unsigned long int 0~(2^32 - 1) 即 0~4294967295

char至少8位,short至少16位,long至少32位。至于缺省的int究竟是16位还是32位由编译器决定,通常这个缺省值是这种机器最为自然的位数,也就是跟机器相关。

注意:上述表格只是类型的最小范围,实际上由于整数是用补码表示的,所以有符号整数类型实际的范围是:
-(2^(bitWidth - 1))~(2^(bitWidth - 1)) - 1.比如short的范围:-32768~32767

一般情况下在64位机器上:

1
NSLog(@"sizeof(char):%lu, sizeof(short):%lu, sizeof(int):%lu, sizeof(long):%lu, sizeof(float):%lu, sizeof(double):%lu", sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(float), sizeof(double));

打印:

1
2
3
4
5
6
sizeof(char):1, 
sizeof(short):2,
sizeof(int):4,
sizeof(long):8,
sizeof(float):4,
sizeof(double):8

是符合ANSI C规范的。

类型溢出

由于整型类型表示的数据范围有限,所以不管是有符号还是无符号整型类型都有可能发生溢出。发生溢出时的结果是未定义的,但一般是会回绕。

char和unsigned char回绕:255+1=0,0-1=255.

1
2
3
4
5
|--->        <---|
-128-----0-------127

|---> <---|
0------127------255

如何避免类型溢出?

要先进行类型转换把小类型转为大类型,再执行操作,才能避免溢出。当然如果数字非常大,那么整数类型就都不能用了,可以考虑使用字符串操作。

类型溢出例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void test_signed_int_overflow(void) {
/**
a = -2147483648
b = 2147483647
c = -2147483648
d = -2147483648
e = 2147483648
*/
int a = INT_MIN; //补码为1000 0000,-a补码依然是1000 0000,所以c,d,a相等
printf("a = %d\n", a);
int b = INT_MAX;
printf("b = %d\n", b);
int c = -a;
printf("c = %d\n", c);
long d = -a;
printf("d = %ld\n", d);
long e = -(long)a; //要先进行类型转换再执行操作,才能避免溢出。当然如果数字非常大,那么整数类型就都不能用了,只能考虑使用字符串操作了。
printf("e = %ld\n", e);

if (c == a) {
printf("overflow\n");
}
if (d == a) {
printf("not work\n");
}
}

加减乘除都有可能导致类型溢出。类型溢出问题很难发现,比如上面的取负操作,由于int的最小值是-2147483648而最大值只有2147483647,那么对-2147483648取负就会导致int溢出。解决办法就是先强转类型再取负:long e = -(long)a;
很多算法题也是考察大数问题,题目看起来会非常简单,比如打印1…n,两个整数相加等等。涉及到整型类型的简单题目一般就是考察大数问题了。

大部分语言对溢出的处理都是回绕,这其实是很危险的,这会导致判断条件失效,进而出现许多诡异bug。Swift在出现类型溢出时程序会直接崩溃,避免了错误逻辑的执行,我觉得是不错的解决办法。

觉得文章有帮助可以打赏一下哦!