《C解毒》试读暨意见征求
C语言运算符优先级普遍存在的一个深层次误区
国内许多C语言教科书,在介绍C语言运算符时,都把所谓的“单目运算符”归纳为优先级相同结合性从右向左的运算符。例如号称是“我国广大初学者学习C语言程序设计的主流用书”的《C程序设计》(谭浩强著,清华大学出版社,2010年6月(第四版))的378页附录D 运算符和结合性 中就是这样:
附录D 运算符和结合性
优先级 运算 含义 要求运算对象的个数 结合方向
1
( ) 圆括号 自左至右
[ ] 下标运算符
-> 指向结构体成员运算符
. 结构体成员运算符
2 1
(单目运算符) 自右至左
! 逻辑非运算符
~ 按位取反运算符
++ 自增运算符
-- 自减运算符
- 负号运算符
(类型) 类型转换运算符
* 指针运算符
& 取地址运算符
sizeof 长度运算符
殊不知,这种归纳是完全错误的。而且恰恰由于《C程序设计》是所谓的“主流用书”,其错误带来的影响也是广泛普遍的和灾难性的。(google或百度一下“所有的单目运算符具有相同的优先级”,你就会知道我是不是在夸大其词危言耸听)。
为了揭示“所有的单目运算符具有相同的优先级”的错误,下面首先按照这种错误的说法进行一个实验。
我们都知道,对于
int i;
来说,&i是求得一个指向i的指针(注意这里的“&”是一个“单目运算符”),&i的数据类型显然是“int *”。
如果对“int *”类型的表达式“&i”做“(int *)”类型转换运算(可能显得有点无聊)
(int *)&i
得到显然还是“&i”——值和类型都没有任何改变。
按照“所有的单目运算符具有相同的优先级”这个错误的说法,由于“&” 和“(int *)”的结合性从右向左
(int *)&i
这个表达式没有任何毛病,也不需要通过加“()”来明确运算对象。
现在,再对
(int *)&i
这个表达式做sizeof运算,由于sizeof也和(int *)同级(注意这是错误的),结合性从右向左,所以可以直接把sizeof写在(int *)&i 的左面,即
sizeof (int *) & i
显然,这个表达式的运算结果和sizeof (int *)应该一模一样,因为(int *) & i的数据类型是(int *) 。
然而,如果你在机器上跑一下下面的代码的话
#include <stdio.h> #include <stdlib.h> int main( void ) { int i ; printf(" %u \n" , sizeof (int *) ) ; printf(" %u \n" , sizeof (int *) & i ) ; system("PAUSE"); return 0; }
你很可能会惊讶地发现,两者并不相同。如果发现两者相同说明你人品可能太好了(这事情很难遇到),这时你可以给i一个初值比如2,再试一次,最终你一定会发现两个貌似“应该”相同的值却根本不相同。
做为目睹了悖论产生过程的观众,我想你非常清楚,共同参与制造这个荒唐悖论的只有编译器、我和“主流用书”中的所谓“所有的单目运算符具有相同的优先级”的理论。所以如果你同意C语言本身应该是一个严密的逻辑体系的话,你应该能想到产生这个悖论的原因只能是三者之中至少有一个犯了错误。
编译器BUG?你觉得可能吗?如此简单的表达式都算错,编译器厂家还怎么混?
我的推理过程有错?可能吗?这比前一条更加没可能。
所以,错误只可能出现在“所有的单目运算符具有相同的优先级”这句话上。
这句话的究竟有什么错误呢?错误就在于,“(类型)”这种运算符的优先级其实低于sizeof 和 一元 & 运算符。由于类型转换运算符的优先级低于sizeof,所以
sizeof (int *) & i
不可能表示sizeof ((int *) & i)这样的含义,因为C语言的优先级和结合性规定只容许运算符作用于高级表达式或同级表达式。
且住,“高级表达式”和“同级表达式”是啥你可能看不懂,因为这是我为了叙述方便发明的新术语。不过咱不是那种发明术语而不做任何解释管杀不管埋之学风恶劣之人。我解释一下,高级表达式是指相对某运算符来说,只出现更高优先级运算符的表达式或基本表达式。例如,对于(二元)+运算符来说,3*5就是高级表达式。同级表达式是指相对某运算符来说,不出现更低级运算符的表达式。例如对于(二元)+运算符,2+3就是它的同级表达式,但a=b就不是,因为这里出现了=,=的优先级比+要低。
举例来说,由于(二元)+的优先级低于(二元)*,那么可以对
3*6 进行 + 运算:3*6+2
再如由于+和+优先级相同,所以可以对3+6做+运算
3+6+2
但不可以对3+6做*运算
3+6*2
虽然合法,但绝对不可能是(3+6)*2的含义。
当把一个运算符添加在高级表达式或同级表达式上是还必须遵守结合性的规定,由于(二元)+运算的结合性是从左到右,所以只能加到高级表达式或同级表达式的右边。当然还得给它加的另一个操作数,这个操作数必须是高级表达式。
由于 (int *) & i 不是 sizeof 的高级表达式或同级表达式,所以希望对它做sizeof运算必须加括号,写成sizeof ((int *) & i)。(注:((int *) & i)构成了一个基本表达式)
而写成 sizeof (int *) & i 的话,就如同前面在3+6加上*一样不是(3+6)*2的含义而是3+(6*2)的含义一样,表达的可能是另一种含义,这个含义是
( sizeof (int *) ) & i
这里&其实是二元&运算。
既然是&是二元&运算,前面代码中没有给 i 初值 显然不妥,正确的代码是:
#include <stdio.h> #include <stdlib.h> int main( void ) { int i = 3 ; //whatever printf(" %u \n" , sizeof (int *) ) ; printf(" %u \n" , sizeof (int *) & i ) ; return 0; }
补充:
根据C标准
优先级为第2级的一元运算符有:
++(前缀)
--(前缀)
sizeof
& * + - ~ !
优先级为第3级的运算符为:
Cast operators (类型转换运算符)