C语言中TMin的写法
在看《深入理解计算机系统》第二版中文版时(Computer Systems A Programmer's Perspective Second Edititon),看到48页第二章网络旁注中提到:
C语言中,将TMin32(32位有符号整数的最小值)写成 -2147483647-1。为什么不简单地写成 -2147483648 或者 0x80000000 ?
书中提到是由于补码表示的不对称性和C语言转换规则之间奇怪的交互。补码表示不对称性CSAPP讲解的通俗易懂,但这里面涉及到什么样的C语言转换规则,书中却没有说明。
在这篇博文的写作过程中,搜到了一些很有用的资料,请见本文末尾的参考资料一节。本文中很大部分内容都是参照 CS:APP Web Aside DATA:TMIN:
Writing TMin in C 一文来写的,然后根据自己的理解添加了一些细节上的东西,力求能够更加通俗易懂一些,这就是本文存在的意义。
C语言中整型常量的实际类型
首先来看一下C语言中整型常量的定义。
C99标准中 6.4.4.1 Integer constants 中提到:
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.
可见如果不发生溢出,整型常量的值总是非负数。如果前面出现符号,则是对整型常量使用的一元运算符,而不是整型常量的一部分。
整型常量的实际类型取决于长度、基数、后缀字母和C语言实现确定的类型表示精度。确定整数常量类型的规则比较复杂,并且在非标准C、C89和C99中是不相同的。具体规则可见<<C语言参考手册》第五版 第二章 2.7.1 整型常量一节。对于本文中提到的场景,下面的这个整数常量的类型表就够用了。
表一:整数常量的类型
ISO C90 | ISO C99 | ||
十进制(Decimal) | 十六进制(Hexadecimal) | 十进制(Decimal) | 十六进制(Hexadecimal) |
int
long
unsigned
unsigned long
|
int
unsigned
long
unsigned long
|
int
long
long long
|
int
unsigned
long
unsigned long
long long
unsigned long long
|
根据C语言版本和常量的格式(十进制和十六进制),常量的数据类型是从上面表格里选择第一个最合适(能表示常量而不溢出的)的类型。
对于ISO C90,编译器依次尝试int 、long、 unsigned(32位机器上long跟int一样,是32位), 最终选择unsigned来表示。对于 2147483648 和 -2147483648,如果表示为32位的二进制数字,它们的位表示是一样的,都是0x80000000。所以这个常量表达式(-2147483648)的数据类型为unsigned且值为 2147483648。
对于ISO C99,编译器依次选择 int、long、long long,最终选择long long类型才能容纳 2147483648 。用64位,可以唯一表示 2147483648 和 -2147483648,所以这个常量表达式的数据类型为long long,值为 -2147483648。
对于16进制常数 0x80000000(注意,按照C语言中整型常量的定义,这个整数常量是正数,值为2417483648),在32位机器上,编译器也是利用同样的规则,依照表一中的16进制的列表来处理。两个语言标准中,都是首先跟TMax32(0x7FFFFFFF)比较,由于0x80000000更大,所以这个值不能用int来表示。接下来和UMax32(0xFFFFFFFF)比较,由于比它小一些,所以选择unsigned来表示。所以这个常量表达式的数据类型是unsigned,值为0x80000000(或者说,是等于2147483648)。
在64位的机器上,事情稍微有些不同。两个语言标准中,十进制的格式 -2417483648 都是long(64位)类型,值为 -2417483648,然而十六进制格式 0x80000000 都是unsigned类型,值为0x80000000(或者说,是 2147483648)。
用一句话来解释C语言中TMin32的古怪写法的原因:虽然-2147483648 这个数值能够用int类
型来表示,但在C语言中却没法写出对应这个数值的int类
型常量。
C语言中如何正确表示TMin32呢?
C语言中limits.h中定义了如下两个宏:
#define INT_MAX 2147483647 #define INT_MIN (-INT_MAX - 1)
ISO C99在stdint.h文件中定义了一些宏 INTN_C、UINTN_C、INTMAX_C与UINTMAX_C,提供对整型常量的长度与类型的可移植性控制。
知道这个有什么用?:
1. 考虑如下代码:
int dcomp = (-2147483648 < 0); int hcomp = (0x80000000 < 0);
请大家思考一下dcomp和hcomp的值是0还是1?需要考虑编译器指定的C语言版本和机器位数(字的大小)。
gcc 中,C模式默认的C语言标准是gnu89(ISO C90, 包括一些C99特性)。可以通过 -std 选项来指定C语言的版本。
MSVC支持C90,只支持部分的C99特性。
2. 考虑如下代码:
int dtmin = -2147483648; int dcomp2 = (dtmin < 0); int htmin = 0x80000000; int hcomp2 = (htmin < 0);
如果你亲自在32位和64位机器上用ISO-C90和ISO-C99版本来编译运行一下,会发现,所有情况下,dcomp2和hcomp2的值都是1。你能解释一下为什么吗?
广告一下吧,在搜 TMin 资料的时候,发现了一本书《一站式学习C编程》,网上有第一版的html的版本,我只看了其中介绍整型 的一节,发现作者写的通俗易懂,而且是国人写的,必须得支持啊。
参考资料:
C语言参考手册第五版 2.7.1 整型常量
深入理解计算机系统 2.2.3 补码编码
补充阅读:
由于补码的不对称性,导致在用补码来表示负数的编译器中使用abs()函数会有问题
如果TMin32/-1,也会导致同样的问题
如果您看了本篇博客,觉得对您有所收获,请点击右下角的“推荐”,让更多人看到!