扭曲的INT_MIN
这个问题是在CSAPP上看到的,详情可以看这个。
简单来说,问题就是:在用补码表示有符号整型的机器上,C语言中的整型的最大值和最小值的表示方法不同。接下来就以32位int为例子讲解这个问题。
int的最大值INT_MAX和int的最小值INT_MIN,是这么定义的:
1 |
熟悉补码的童鞋应该都知道,32位的数据,能表示的最大的整数的位级表示是0x7FFFFFFF
,即2147483647
,最小的整数的位级表示是0x80000000
,即-2147483648
。那么是为什么不能直接#define INT_MIN -2147483648
或者#define INT_MIN 0x80000000
?
CSAPP上是这么来说的,我觉得总结的很好:
不幸的是,补码表示的不对称性和C语言转换规则之间这种奇怪的交互,迫使我们使用奇怪的方式来写Tmin。虽然理解这个问题需要我们钻研C语言标准中一些隐晦的角落,但是它也能帮助我们理解整数数据类型和表示的一些细微之处。
接下来以我自己的理解来阐述一下原因。
首先,C语言里有整数常量(integer constant
),让整型数值直接在表达式中使用。比如说int a = 10
这里出现的10
,就是个整型常量。整型常量除了默认的十进制,可以在开头加0
表示八进制,比如010
就是表示的八进制数字10
;还可以在开头加0x
或者0X
表示十六进制,比如说0xFF
就表示十六进制数字FF
。
而且integer constant
可以添加后缀。可选的后缀有u
或者U
,l
或者L
,ll
或者LL
。带u
和带l
的后缀可以进行组合(而且不用分先后顺序)。于是这样就有了6种后缀方式:
- 无后缀
u
或者U
l
或者L
ll
或者LL
- 既有
l/L
又有u/U
- 既有
ll/LL
又有u/U
integer constant
有自己的类型。而决定每个 integer constant
类型的规则有点复杂。上面提到的3种进制方式和6种后缀方式一共带来了18种组合,每个组合都会有一个由若干个整数类型组成的候选列表,会依次匹配,第一个能容纳这个数值的类型就是这个 integer constant
的类型。完整的表格可以上cppreference上看。
现在再来看INT_MIN
。如果#define INT_MIN -2147483648
,由于没有负整数常量,那么首先来判断2147483648
的类型。它属于无后缀的十进制,从C99开始,候选列表为:
- int
- long int
- long long int
而在int是32位补码表示的机器上,int最大为 2147483647
, 2147483648
会被当成long int
类型或者long long int
类型,具体情况由 data models 来定。总而言之,就无法用 -2147483648
来表示最小的int值了。
#define INT_MIN 0x80000000
也是类似的情况。首先来判断 0x80000000
的类型。它属于无后缀的十六进制,候选列表为:
- int
- unsigned int
- long int
- unsigned long int
- unsigned long long int
在 int 是32位补码表示的机器上,int最大为 2147483647
,即 0x7FFFFFFF
,小于 0x80000000
。所以也不能用 int 来表示 0x80000000
,自然就无法用 0x80000000
来表示最小的int值了。
在网上看到的一句话总结,我觉得说的挺有道理:虽然 -2147483648
这个数值能够用int类型来表示,但在C语言中却没法写出对应这个数值的int类型常量。