C 语言标准库中定义了整型数的最大值和最小值,以 GCC 编译器为例,其定义如下:
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX-1)
这里产生一个疑问:为什么INT_MIN
不直接定义成 -2147483648,而是写成 -2147483647-1 这种形式呢?
这里涉及到 C 语言如何定义常量,C 语言标准文档对常量的解释如下:“An integer constant begins with a digit, but has no period or exponent part. It may have a prefix that specifies its base and a suffix that specifies its type.”
大概意思是“一个常量起始于一个数字,但是不包含小数点或指数的部分。并且可能有一个用于指定数字基底的前缀和一个指定类型的后缀。”这里的前缀比如0xFF
中的0x
,后缀比如100UL
中的U
和L
。从整型常量的词法定义也可看出:
如果将INT_MIN
定义为 -2147483648,这里的 -2147483648 其实是由一元运算符-
和常量 2147483648 组成。编译器遇到常量会按照定义好的变量顺序一一匹配其类型(如果指定了后缀,会从指定类型开始匹配),这个定义好的顺序在 C 语言规范文档里可以查到。
常量 2147483648 不带后缀,从int
类型开始匹配,因为 2147483648 (2^31) 超过了int
的最大取值范围 2147483647(2^31-1),为了保证数值正确性,需要表示范围更大的类型,编译器继续向下匹配到long int
,其最大取值范围是 2^63-1 足以表示常量 2147483648,所以常量 2147483648 的最终类型被确定为long int
。虽然最小值是正确的,但是类型却不正确,这导致的结果是sizeof INT_MIN
的大小是 8 字节而非 4 字节,此外在进行 x < INT_MIN
这样的比较操作时,x 会被提升到long int
类型,这些显然都是不正确的行为。而如果使用常量-2147483647-1
定义INT_MIN
,其可以被正确的识别为 int 类型。
下面是一组测试代码,演示了两种表示方法虽然最小值一样,但是类型却不同。
#include
int main() {
// 2147483647 在 int 的表示区间内,类型为 int,大小为 4 子节
printf("%d, %d\n",-2147483647-1, sizeof(-2147483647-1));
// 2147483648 超出 int 的最大值 2147483647,类型提升为 long int,大小为8子节
printf("%d, %d\n",-2147483648, sizeof(-2147483648));
return 0;
}
运行结果
-2147483648, 4
-2147483648, 8
小结一下,通过分析 C 标准库中INT_MIN
的宏的奇怪定义,发现其中牵扯出了 int 类型的取值范围、常量类型分配、类型提升等知识点。
参考
- 《深入理解计算机系统》第2章:数据的表示
- 探究 C 语言标准库 limits.h 关于INT_MIN 宏的奇怪定义