免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 11726 | 回复: 37
打印 上一主题 下一主题

一段关于运算符和结合性奇怪的程序(高人请教) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-12-26 11:08 |只看该作者 |倒序浏览
1 #include <iostream>
  2 using namespace std;
  3
  4 int main(void)
  5 {
  6         int     C=0;
  7
  8         if(++C==C++)
  9                 cout<<"yes"<<endl;
10         cout<<C<<endl;
11
12         int     D;
13         C=D=C++;
14         cout<<C<<endl<<D<<endl;
15         
16         return 0;
17 }


为什么判断的结果为真,又问什么C和D输出的结果不一样?

论坛徽章:
0
2 [报告]
发表于 2010-12-26 11:20 |只看该作者
++X 是先自加后运算,
X++ 是先运算后自加

至于++C==C++这种语句要看机器运算器逻辑.

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2010-12-26 11:30 |只看该作者
D和C肯定不一样啦。先赋值给D后,然后C自加。

论坛徽章:
0
4 [报告]
发表于 2010-12-26 13:54 |只看该作者
D和C肯定不一样啦。先赋值给D后,然后C自加。
L_kernel 发表于 2010-12-26 11:30



    C是自加了,但C不是重新赋值为D了么?那应该也还是一样啊

论坛徽章:
0
5 [报告]
发表于 2010-12-26 14:01 |只看该作者
++X 是先自加后运算,
X++ 是先运算后自加

至于++C==C++这种语句要看机器运算器逻辑.
gu_wh 发表于 2010-12-26 11:20



    ++C==C++这个比较应该就是相等的吧。

++C返回自增后的C也就是1
C++先返回C的值,再自增
那么比较的时候就还是相等的

有点不明白C=D=C++的运行结果。

D=C++这个表达式会有返回值么?

论坛徽章:
0
6 [报告]
发表于 2010-12-26 15:29 |只看该作者
本帖最后由 KBTiller 于 2010-12-26 17:30 编辑

我的看法
《狂人C》144~146
3.        使用“++”的常见错误
到目前为止,我们终于将两种“++”运算符不同的运算含义解释清楚了。然而,在现实的运用中,这个两个“++”运算符还是会经常让我们感到头痛,甚至很多成熟的编程团队会将其定义为尽量避免使用的运算符。那么,这个谜一样的运算符,为什么会成为不少程序员极力回避的禁忌呢?
我们首先来看看下面这个简单的代码片段。
i = 3;
j = ++ i + i;
按照前面我们讲过的原理,在第二行的表达式里,CPU读取第一个i值(求++i的值)之前,需要完成将i赋值为i+1这个副效应。但问题在于,在前后两个序点之间,CPU需要两次读取i的值,我们并不清楚会先读哪个i,这个次序选择权在于编译器。我们根本无法控制。也就是说,上面那句话可能表示两种不同的运算语意,产生两种完全不同的运算结果。
语意一
完成副效应i=i+1(i的值变为4)
读第一个i值(此时赋值的副效应已经完成,i的值为4)
读第二个i值(i值已经变为了4,这个i值自然也不例外)
两次读得的i值相加,把结果写入j内存(结果即是8)
语意二
读第二个i值(此时i值为3)
完成副效应i=i+1(i的值变为4)
读第一个i值(此时赋值的副效应已经完成,i的值为4,于是出现了第一个i值和第二个i值之间的值并不相等的现象)
两次读得的i值相加,把结果写入j内存(结果竟然是7)
由于C语言并未明确规定这些运算的次序,因此在完全符合C语言语法规则的前提下,竟然能得到两种结果,这就是所谓的“二义性”。
编写程序时候是不可能容忍代码存在这种“二义性”的,否则程序很可能就变成了“鸡同鸭讲”。代码必须具备唯一确定的语意。
除了涉及到序点,C语言没有规定编译器在这种情况下应该究竟选择哪种语意,这样,表达式 j = ++ i + i就成了一种未定义行为。如同前面曾经提到的那样,这种未定义行为尽管不违背C语言的语法规则,但本质上却是一种错误的代码。
以这里讨论的表达式为例,在求“+”运算符右边的i值的时候,从C语言或代码的角度来说,并不能确定i在内存中确切的值。因为在求“+”左面的操作数——表达式“++i”的值的时候可能改变i的值。由于没有规定求“++i”的值和求“+”右边的i值这两个动作之间的次序,于是求表达式“++ i + i”的值就成了一个未定义的行为。
未定义的行为出现在代码中,就是一个“语病”。只不过这里我们说的“语病”不是那种不符合语法要求的语病,而是那种语法上符合要求,但在语言或代码层面却无法确定其唯一含义的语病。比如,有一个大家很熟悉的广告词――“xx皮鞋,足以自豪的皮鞋”,语法上这句话绝对没有问题,但那个“足”字显然是一语双关的,作为广告语这很好,但编程不是做广告,计算机也不会听你忽悠,它只接受具体明确的、不带有“二义性”的指令。而代码中没有语法错误的“二义性”会导致编译器为你“胡乱”选择一种语意。这当然是不可接受的。
根据程序运算结果揣测j = ++ i + i这样未定义行为没有确定含义的表达式的含义是肤浅幼稚的。因为未定义行为不但是不可能预测的,同样也不可以逆向推测。它产生什么样的后果都不奇怪,哪怕让机器死机,关闭电源甚至火山爆发。C语言的学习者之间经常会出现很多类似这样的可笑对话:一个学习者问,为什么这个计算机(编译器)说“足以自豪的皮鞋”里面的“足”字是“脚”的意思,而不是“足够”的意思?另一个学习者立刻反驳,不对!我的计算机(编译器)明明说“足”是足够的意思嘛!
这两个不明就里的学习者也许会争论上好一阵子,却也得不出一个所以然来。本书的读者对此应该有个清晰的认识,能够很轻松地告诉他们代码“二义性”的来龙去脉。
下面,列出了一些C语言中典型的“二义性”例子。
int i = 3,j;
j = (++i)+(++i)+(++i);
(i++)+ (i++)+(i++)
i = i++
printf(“%d %d\n”, i , i++ );
p=(++p>0)?(p++) :p++);
j = (i = 4) + (i = 5) ;
执行 int k = 11 ; k = 1/3*k++;后,k的值是____。
a += a -= a * a
这些例子,都会让编译器陷入那个“足”是脚还是足够的疑惑。写出这种表达式的人,说明其对于运算符的真实含义还是缺乏了解。可惜的是,在现在国内很多专业的C语言论坛中,还是会有不少程序员,在这个问题上疑惑不解。
这些人往往都还有另一个误区,这个误区就是把优先级和结合性与运算次序相混淆,他们难以理解为什么优先级高的反而后计算。比如下面的表达式:
j + i ++
在这个表达式中,“++”的优先级最高,但这个运算却不是最先进行的。这里的优先级只是决定了“++”这个运算符的运算对象是i,而不是“j+i”,即:
j + (i ++ )
也就是说这个表达式的意义是计算“j+ i”的值,再加上一个副效应。而这个副效应发生的时间,我们只知道会是在编译器求完i值之后,但我们无法知道会是发生在计算“j+i”值的之前还是之后。
然而,不少人把优先级理解成了小学里的“先乘除后加减”,这是完全的误解。这里需要再次强调的是,优先级和运算次序完全是两回事!
对于初学者来说,另外一个错误不得不提。就是,++或--(无论前缀或后缀),只能用于左值。比如,int i; “++i”是可以的,因为i是左值;但++(i+1)是一个语法错误。因为(i+1)只有值的含义不可能表示一块连续的具有类型含义的内存(左值),因此(i+1)只是一个右值表达式。在目前这个学习阶段,只有变量名这种初级表达式是左值表达式。
4.        总结
好了,现在我们完全搞清了“++”运算符的来龙去脉。那么,在代码中应该如何避免上面所提到的“二义性”问题呢?
首先,我们需要把握一个原则,即不在两个序点之间更改同一个变量(严格的术语是对象)两次或更多次(a += a -= a * a就是违背了这种原则的错误代码)。如果两个序点之间只写一次同一对象的值,但同时还存在着读这个对象值的情况,那么必须确保写这个对象的值发生在读这个对象值之后。所以,表达式 i = i + 1 的行为是确定的,而表达式++ i + i则属于未定义的行为。
其次,尽量少使用可能引发“二义性”的复杂表达式。熟练的程序员在使用“++”这类运算符时是极其审慎的,在利用“++”的副效应时,一定要确保不会发生出乎自己意料之外的结果。
或许有人会问,这么麻烦干什么,直接取消可恶的“副效应”不久可以了吗?然而,“副效应”真的那么可恶吗?是否取消了副效应就可以一了百了了呢?其实不是的。
副效应不一定是什么坏事。比如前面例子中for语句中的“++”就是利用了其将i值加1的副效应使得代码写得非常简洁,而求得的i值本身倒是没有什么用处的。
而且,没有副效应的表达式语句,在编译器看来是可以不理睬的废话。比如:
2 + 4 ;
这句话,几乎所有的编译器都不会执行。我们最常用的printf()函数,其实多数情况下使用的是它的副效应,而函数调用得到的值几乎很少被用到。编译器对这样有副效应的表达式语句不可能置之不理。
因此,副效应是非常有用的,有时候甚至是必须的。作为一个合格的程序员,应该善于使用副效应。但是在涉及到改变变量在内存中的值的表达式中,一定要慎重,否则就会像前文中那些例子一样,画虎不成反成犬。

论坛徽章:
0
7 [报告]
发表于 2010-12-26 15:36 |只看该作者
回复 1# venkee
  也就是说,我认为这段代码本身就是错误的,没办法讨论
  ( sagasw网友反对说这段是错误的代码,但他说“标准未定义的代码也是应该避免的"。)

论坛徽章:
0
8 [报告]
发表于 2010-12-26 16:24 |只看该作者
回复 7# KBTiller

您谈论Cpu先读取哪个C值,但在这里并不存在这个先后问题,因为这里做的是赋值二元运算,不需要理会左值大小,并不像加法那种二元运算要先确定左右两个值

论坛徽章:
0
9 [报告]
发表于 2010-12-26 17:09 |只看该作者
本帖最后由 KBTiller 于 2010-12-26 17:12 编辑

回复 8# venkee


   
  1. (++C==C++)
复制代码

这已经违反不在两个序点之间更改同一个变量两次的原则

论坛徽章:
0
10 [报告]
发表于 2010-12-26 19:38 |只看该作者
回复 9# KBTiller


    判断那里确实是这样,但赋值那里就不是你所说的那样了,你不能说赋值那段操作具有二义性
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP