2.2.5 C 语言中的有符号数与无符号数
2.2.5 C 语言中的有符号数与无符号数
如图 2-9 和图 2-10 所示,C 语言支持所有整型数据类型的有符号和无符号运算。尽管 C 语言标准没有指定有符号数要采用某种表示,但是几乎所有的机器都使用补码。通常,大多数数字都默认为是有符号的。例如,当声明一个像 12345 或者 0x1A2B 这样的常量时,这个值就被认为是有符号的。要创建一个无符号常量,必须加上后缀字符 U 或者 u,例如,12345U 或者 0x1A2Bu。
C 语言允许无符号数和有符号数之间的转换。虽然 C 标准没有精确规定应如何进行这种转换,但大多数系统遵循的原则是底层的位表示保持不变。因此,在一台采用补码的机器上,当从无符号数转换为有符号数时,效果就是应用函数 U2Tw,而从有符号数转换为无符号数时,就是应用函数 T2Uw,其中 w 表示数据类型的位数。
显式的强制类型转换就会导致转换发生,就像下面的代码:
int tx, ty;
unsigned ux, uy;
tx = (int) ux;
uy = (unsigned) ty;另外,当一种类型的表达式被赋值给另外一种类型的变量时,转换是隐式发生的,就像下面的代码:
int tx, ty;
unsigned ux, uy;
tx = ux; /* Cast to signed */
uy = ty; /* Cast to unsigned */当用 printf 输出数值时,分别用指示符 %d、%u 和 %x 以有符号十进制、无符号十进制和十六进制格式输出一个数字。注意 printf 没有使用任何类型信息,所以它可以用指示符 %u 来输出类型为 int 的数值,也可以用指示符 %d 输出类型为 unsigned 的数值。例如,考虑下面的代码:
int x = -1;
unsigned u = 2147483648; /* 2 to the 31st */
printf("x = %u = %d\n", x, x);
printf("u = %u = %d\n", u, u);当在一个 32 位机器上运行时,它的输出如下:
x = 4294967295 = -1
u = 2147483648 = -2147483648
在这两种情况下,printf 首先将这个字当作一个无符号数输出,然后把它当作一个有符号数输出。以下是实际运行中的转换函数:T2U32(-1) = UMax32 = 232 - 1 和 U2T32(231) = 231 - 232 = -231 = TMin32。
由于 C 语言对同时包含有符号和无符号数表达式的这种处理方式,出现了一些奇特的行为。当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么 C 语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。就像我们将要看到的,这种方法对于标准的算术运算来说并无多大差异,但是对于像 < 和 > 这样的关系运算符来说,它会导致非直观的结果。图 2-19 展示了一些关系表达式的示例以及它们得到的求值结果,这里假设数据类型 int 表示为 32 位补码。考虑比较式 -1 < 0U。因为第二个运算数是无符号的,第一个运算数就会被隐式地转换为无符号数,因此表达式就等价于 4294967295U < 0U(回想 T2Uw(-1) = UMaxw),这个答案显然是错的。其他那些示例也可以通过相似的分析来理解。

练习题 2.21 假设在采用补码运算的 32 位机器上对这些表达式求值,按照图 2-19 的格式填写下表,描述强制类型转换和关系运算的结果。
| 表达式 | 类型 | 求值 |
|---|---|---|
| -2147483647-1 == 2147483648U | ||
| -2147483647-1 < 2147483647 | ||
| -2147483647-1U < 2147483647 | ||
| -2147483647-1 < -2147483647 | ||
| -2147483647-1U < -2147483647 |
网络旁注 DATA:TMIN:C 语言中 TMin 的写法
在图 2-19 和练习题 2.21 中,我们很小心地将 TMin32 写成 -2147483647-1。为什么不简单地写成 -2147483648 或者 0x80000000?看一下 C 头文件 limits.h,注意到它们使用了跟我们写 TMin32 和 TMax32 类似的方法:
/* Minimum and maximum values a 'signed int' can hold. */ #define INT_MAX 2147483647 #define INT_MIN (-INT_MAX - 1)不幸的是,补码表示的不对称性和 C 语言的转换规则之间奇怪的交互,迫使我们用这种不寻常的方式来写 TMin32。虽然理解这个问题需要我们钻研 C 语言标准的一些比较隐晦的角落,但是它能够帮助我们充分领会整数数据类型和表示的一些细微之处。