1 c编译器怎么确定数值常量的类型
首先明确一下这里所说的数值常量指的是程序中的一个数字,比如:
if (1 < 2)
{
}
其中1
、2
就是数值常量。
就以数字1
为例,1
在很多类型的表示范围之内,比如short
、int
、unsigned int
等,那么编译器会将1
解读成哪个类型呢?这是由c标准规定的,对于不同版本的c标准,相关规定的具体内容有所差异,对于c90,相关规定如下表所示:
范围 | 类型 |
---|---|
0~231-1 | int |
231~232-1 | unsigned int |
232~263-1 | long long |
263~264-1 | unsigned long long |
而对于c99,相关标准有了些变化:
范围 | 类型 |
---|---|
0~231-1 | int |
231~263-1 | long long |
263~264-1 | unsigned long long |
细心的同学可能会发现,上表中没有给出负值的情况,比如-10
。编译器在遇到负值的时候,先不看符号,而是根据数字来确定类型,然后再处理负号。仍旧拿-10
打比方,编译器根据10
来确定类型为int
,然后再处理负号。
2 c语言数值常量的陷阱
为什么要强调数值常量的类型呢?因为这里面有一些陷阱需要注意。以c90标准来说,考虑如下的表达式:
-2147483648 < 2147483647
很多人第一反应都是这个表达式为true
,毕竟从数值上看这是显然的。但是,支持c90标准的编译器可不这么认为!关键是-2147483648
,编译器根据2147483648
也就是231,确定这个数值的类型为unsigned int
。再考虑负号,根据负数的补码的转换规则:对应正数(绝对值)按位取反再加1:
8000_0000 按位取反⇒ 7fff_ffff 再加1⇒ 8000_0000
就这样,编译器把-2147483648
硬是给处理成了unsigned int
类型的2147483648
,而2147483648
是大于2147483647
的。但是编译器又不能把2147483648
解读成int
类型,因为补码表示是不对称的,int
类型最小可以表示到-2147483648
,但最大只能表示到2147483647
。如果能把2147483648
表示成更大的有符号类型比如long long
就不会有上述问题了,c99标准正是这么做的。不过为了写出不同版本的标准下,行为一致的程序,我们还是要注意这个细节。
为了加深理解,不妨再来看几个例子(仍然根据c90标准):
例1
int i = -2147483648;
if (i < 2147483647)
{
printf("-2147483648 < 2147483647\n")
}
此时,会打印出-2147483648 < 2147483647。上文已说过,编译器会把-2147483648
解读成一串二进制0x8000_0000 ,因此变量i所在内存存储的也是这一串二进制,但不同的是,因为i是int
类型,因此会以int
来看待这串二进制,一个负数自然是小于2147483647的。
例2
-2147483647 - 1 < 2147483647
对于-2147483647
,编译器根据2147483647
确定其为int
类型,然后处理负号:
7fff_ffff 按位取反⇒ 8000_0000 再加1⇒ 8000_0001
-2147483647 - 1
则得到8000_0001 - 1 = 8000_0000
,由于是按int
类型来看,因此8000_0000
是一个负数,且是int
所能表达的最小负数-2147483648
。所以例2的表达式为true
。值得一提的是,INT_MIN通常就定义为:
#define INT_MIN (-2147483647 - 1)
这样一来,这个宏不管在c90还是c99标准都能工作的很好。
例3
if (-2147483648 - 1 == 2147483647)
{
printf("-2147483648 - 1 == 2147483647\n")
}
编译器会将-2147483648
解读成unsigned int
类型的二进制串8000_0000
,而8000_0000 - 1 = 7fff_ffff
,因此例3中条件成立,会执行printf语句。
3 有符号数和无符号数在一起运算
除了让编译器自己去解读数值常量的类型,我们还可以通过在数值常量后面加上后缀来主动告诉编译器数值常量的类型。比如0U
或0u
就是告诉编译器,这是一个unsigned int
的0
。此外,也可以用强制类型转换来实现这一目标,比如(unsigned)0
。
值得关注的是,当无符号数和有符号数一起运算时,c语言会进行类型提升,把有符号数按照无符号数看待。不同类型的数混在一起运算时,有一些值得注意的地方,如下表:
关系表达式 | 类型 | 结果 | 说明 |
---|---|---|---|
0 == 0U | 无符号 | true | 0000_0000 == 0000_0000 |
-1 < 0 | 有符号 | true | -1 < 0 |
-1 < 0U | 无符号 | false | ffff_ffff > 0000_0000 |
2147483647 > -2147483647 - 1 | 有符号 | true | 231-1 > -231 |
2147483647U > -2147483647 - 1 | 无符号 | false | 231-1 < 231 |
2147483647 > (int)2147483648U | 有符号 | true | 231-1 > -231 |
-1 > -2 | 有符号 | true | -1 > -2 |
(unsigned)-1 > -2 | 无符号 | true | 231-1 > 231-2 |