Chinaunix首页 | 论坛 | 博客
  • 博客访问: 139758
  • 博文数量: 22
  • 博客积分: 1326
  • 博客等级: 中尉
  • 技术积分: 258
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-02 00:52
文章分类
文章存档

2012年(1)

2011年(21)

分类: C/C++

2011-07-13 20:51:31

      国内许多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
 
int main( void )
{
  int i  ;
    
  printf(" %u \n" , sizeof (int *)     ) ;
  printf(" %u \n" , sizeof (int *) & i ) ;
      
  system("PAUSE"); 
  return 0;
}

      做为目睹了悖论产生过程的观众,我想你非常清楚,共同参与制造这个荒唐悖论的只有编译器、我和“主流用书”中的所谓“所有的单目运算符具有相同的优先级”的理论。所以如果你同意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
 
int main( void )
{
  int i = 3 ; //whatever
 
  printf(" %u \n" , sizeof (int *)     ) ;
  printf(" %u \n" , sizeof (int *) & i ) ;
 
  return 0;
}
阅读(3078) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

pmerofc2011-07-17 11:35:54

xparmenides: 一个&有两种解释,这的确是C语言容易造成混淆的地方。我查阅了《The C Programming Language》和《C语言大全》,这两本书中给出的运算符的优先级和结合性列.....
《The C Programming Language》是88年左右的书,那时C标准(C89)还没公布。如果我们承认标准,不应该以K&R的为准。况且,K&R的书一向有些不很严谨的地方。当然不是说K&R的书不好,我认为应该学其长避其短。
《C语言大全》在我看来完全不具备权威性,记得作者写过一本关于标准的书(应该说比老谭的强千百倍),被专家们骂的够呛
Turbo C 是1987年左右的产品,不是完全符合C标准的。
如果说C99还没普及,但是说C89已经被普遍认为是工业标准应该没什么异议吧。
所以,谭的东西其实是从一些过时的古董簿子上扒下来的,可笑的是他居然自称他这书是按C99写的,这就属于

xparmenides2011-07-16 13:40:35

一个&有两种解释,这的确是C语言容易造成混淆的地方。我查阅了《The C Programming Language》和《C语言大全》,这两本书中给出的运算符的优先级和结合性列表与老谭给出的是一致的。

我测试运行了一下你的程序,Turbo C 给出的结果是:
2
0
Gcc给出的结果是:
4
4

先分析Turbo C的结果。第一个printf得出2是因为Turbo C运行在16位系统中,其int型变量占2个字节。对于第二个printf,Turbo C看来是按照你的理解,先做了sizeof,再做&运算,二进制形式的计算是:
               0000 0000 0000 0010