我要啦免费统计

[译]在C中表示TMin

在给一同学解决C的一个问题时,偶然发现如果直接输出-2147483648会得到2147483648。一开始我以为是整数溢出,然而输出(int)-2147483648则会得到正确的结果-2147483648。int型数据类型的取值范围是-2^31 ~ 2^31-1,也就是说,int型可以表示-2147483648这个数。那么为什么第一个数会underflow呢?综合Stackoverflow上面热心网友的回答,以及同学提供的一份CSAPP的补充资料,终于解决了这个问题。下面我就把这份同学给出的详细的资料翻译出来,供大家参考。

原文下载:http://www.csapp.cs.cmu.edu/public/waside/waside-tmin.pdf

原作者:Randal E. Bryant and David R. O’Hallaron

在C中表示TMin

1.现象

在图CS:APP2e-2.18和问题CS:APP2e-2.21,我们将TMin_{32}写作-2147483647-1。为什么不简单的将其写作-2147483648或者0×80000000呢?我们可以看一下C的头文件

limits.h,我们会发现这个头文件也用了相似的方法来写TMin_{32}TMax_{32}

不幸的是,C语言中的不对称的补码表示法和转化规则使我们不得不写成这样的形式。虽然要理解这个问题,我们就需要深入C语言标准的那”黑暗一角”,但它的确会使我们明白整数类型与其表示的精妙之处。

tmin32_1

表一:表示整数常量的数据类型。根据语言版本和格式,常量的数据类型将为列表中找到的第一个合适的类型

tmin32_2

表二:TMin_{32}的数据类型结果。根据语言版本和格式,对于两个表达式(Expression)我们能够得到三种不同的数据格式,其中包括值为正的情况。

假设我们把TMin_{32}写成-2147483648并且在32位的机器上面使用图CSAPP2e-2.8的数据大小来编译。当编译器遇到-X形式的数字,它会首先决定X的类型和值,之后再转化为相反数,但值2147483648已经超过int型的上界(这个值已经大于TMax_{32},这就是补码表示法的不对称性)。编译器试图去决定这个值的类型并且分配一个正确的值,于是就去查看表1的表中的十进制栏。如果是ISO C90标准,编译器在遍历int到long,之后再到unsigned,才会发现能够表示-2147483648的类型。而正如我们在CSAPP2e-2.3.3中看到的,2147483648 和 -2147483648有同样的32位表示,所以结果是unsigned类型,并且值为2147483648。如果是ISO C99标准,编译器会遍历int到long到long long类型。如果是64位的机器,2147483648 和 -2147483648 的表示是不同的,所以结果的数据类型是long long,值为-2147483648。

而对于32位机器上的十六进制数0×80000000,编译器通过表一的Hexdecimal列来进行类似的遍历。对于两种语言版本来说,编译器首先将这个数与TMax_{32}(0x7FFFFFFF)对比,因为这个数更大一些,所以编译器决定这个数不能被表示为int类型。编译器之后在把它和UMax_{32}(0xFFFFFFFF)比较,因为比UMax_{32}小,所以编译器选择了unsigned类型。

但是在64位的机器上情况就略有不同。对于两种语言版本,十进制的-2147483648都被转为long型并且赋值-2147483648,而十六进制被转为unsigned型并且赋值0×8000000(即2147483648)。

所有的这些结果都在表二中总结了出来。对于结果类型是long和long long来说,这个数是负值,但却有64位长。对于类型是unsigned来说,这个数是正值并且是32位。这个结果可

以被以下代码验证: 这两行代码分别把TMin_{32}按照十进制或者十六进制数来表示并且判断是否小于0。通过不同的编译器版本和标准调试后,我们会看到dcomp的结果0和1都会出现,说明结果有可能是正的也可能是负的,而hcomp的值却一直是0,说明十六进制下的值一直是负的。看似简单的表示TMin_{32}似乎比我们预期的要难一些。

练习问题1

考虑下列代码: 我们分别在32位和64位的机器上用C90和C99的标准来编译。在所有的情况下,无论是dcomp2还是hcomp2都返回值1,并且其他测试也验证了dtmin和htmin确实等于TMin_{32}

请解释为什么这几行代码没有出现之前的那样的针对编译器和标准不同而不同的状况。

2.解释

对于很多程序,由于不同字长和语言标准而引起的不同不会影响程序的行为(参考练习问题1)。尽管如此,我们还是知道了为什么把TMin_{32}表示为-2147483647-1为产生一个更可靠的结果。因为2147483647TMax_{32}的值,所以能够被表示为int类型,这样就不会再触发表一的转换规则。

练习问题2:

假设我们把TMin_{32}写作-0x7FFFFFFF-1。C编译器会在32位和64位机器和两个语言标准的情况下都将其视作int类型吗?请解释你的理由。

练习问题3:

我们希望构造一个能够简洁表示TMin_w的表达式,其中w是long int类型的位数。因为不同机器的字长都不一样,我们决定使用sizeof操作,这样只要w是8的倍数机器就会生成TMin_w。我么恩也可以用CSAPP2e-2.3.6中的小技巧,把数左移3位也相当于把这个数乘以8。于是我们写下了下面的代码: 我们在32位的机器上测试这个代码,却发现表达式结果为64。

  • 解释为什么会发生这样的状况
  • 这个表达式在64位的机器上会生成什么数值
  • 对这个表达式做尽可能小的改动使它计算正确

习题解答

习题一解答:

在赋值dtmin和htmin时,我们已经隐式将类型转化为32位整型。这样值就是-2147483648,而不管原本常量是signed还是unsigned或者是32位还是64位。

习题二解答:

是的,不管环境如何都是这样。因为0x7FFFFFFF等于TMax_{32},所以会表示为整数int类型。结果也因此是int类型。

习题三解答:

这是一个典型的关于运算优先级的错误。CSAPP2e-2.1.10中提到过,加法和减法的优先级要高于移位的优先级,而移位是左结合的。

A. 考虑到long类型需要4个字节,那么这个表达式就等价于code,即code,结果为64

B. 如果long类型需要8字节时,我们有code,即code,结果为code

C. 这个问题可以通过添加一对括号来解决: 我们也可以把code替换为code,而且更高优先级的乘法会确保计算的正确性。事实上,这会使得代码更可读,并且机器码也会更精确。

Post Footer automatically generated by wp-posturl plugin for wordpress.

Leave a Reply

Your email address will not be published. Required fields are marked *