C,C++写出传输密文C的表达式式求值顺序 裘老的解释

豆丁微信公众号
君,已阅读到文档的结尾了呢~~
中缀表达式求值 表达式求值代码 中缀表达式 前缀中缀后缀表达式 c语言表达式求值 表达式求值 条件表达式求值 java表达式求值 excel 表达式求值 四则运算表达式求值
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
C++程序代码-中缀表达式求值
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='http://www.docin.com/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口C/C++中的序列点
C/C++中的序列点
Table of Contents
这是NetMD@newsmth的, 后面附带了Purusa@newsmth的另一篇文章.
0. 什么是副作用(side effects)
C99定义如下:
Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.
C++2003定义如下:
Accessing an object designated by a volatile lvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.
可以看出C99和C++2003对副作用的定义基本类似,一个程序可以看作一个状态机,在任意一个时刻程序的状态包含了它的所有对象内容以及它的所有文件内容(标准输入输出也是文件),副作用会导致状态的跳转.
一个变量一旦被声明为volatile-qualified类型,则表示该变量的值可能会被程序之外的事件改变,每次读取出来的值只在读取那一刻有效,之后如果再用到该变量的值必须重新读取,不能沿用上一次的值,因此读取volatile-qualified类型的变量也被认为是有副作用,而不仅仅是改写
注,一般不认为程序的状态包含了CPU寄存器的内容,除非该寄存器代表了一个变量,
void foo() {
register int i = 0;
// 变量i被直接放入寄存器中,本文中被称为寄存器变量
// 注,register只是一个建议,不一定确实放入寄存器中
// 而且没有register关键字的auto变量也可能放入寄存器
// 这里只是用来示例,假设i确实放入了寄存器中
// 寄存器内容改变,对应了程序状态的改变,该语句有副作用
// 编译时该语句一般有警告:“warning: expression has no effect”
// CPU如果执行这个语句,也肯定会改变某个寄存器的值,但是程序状态
// 并未改变,除了代表i的寄存器,程序状态不包含其他寄存器的内容,
// 因此该语句没有任何副作用
特别的,C99和C++2003都指出,no effect的expression允许不被执行:
An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
1. 什么是序列点(sequence points)
C99和C++2003对序列点的定义相同
At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
中文表述为,序列点是一些被特别规定的位置,要求在该位置前的evaluations所包含的一切副作用在此处均已完成,而在该位置之后的evaluations所包含的任何副作用都还没有开始.
例如C/C++都规定完整表达式(full-expression)后有一个序列点
extern int i,
上面的代码中i = 0以及j = i都是一个完整表达式,;说明了表达式的结束,因此在;处有一个序列点,按照序列点的定义,要求在i = 0之后j = i之前的那个序列点上对i = 0的求值以及副作用全部结束(0被写入i中),而j = i的任何副作用都还没有开始。由于j = i的副作用是把i的值赋给j,而i = 0的副作用是把i赋值为0,如果i = 0的副作用发生在j = i之后,就会导致赋值后j的值是i的旧值,这显然是不对的
由序列点以及副作用的定义很容易看出,在一个序列点上,所有可能影响程序状态的动作均已完成,那这样能否推断出在一个序列点上一个程序的状态应该是确定的呢?!答案是不一定,这取决于我们代码的写法。但是,如果在一个序列点上程序的状态不能被确定,那么标准规定这样的程序是undefined behavior,稍后会解释这个问题.
2. 表达式求值(evaluation of expressions)与副作用发生的相互顺序
C99和C++2003都规定
Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.
也就是说,C/C++都指出一般情况下在表达式求值过程中的操作数求值顺序以及副作用发生顺序是未说明的(unspecified)。为什么C/C++不详细定义这些顺序呢?原因是因为C/C++都是极端追求效率的语言,不规定这些顺序,是为了允许编译器有更大的优化余地,例如:
extern int *p;
根据前述规定,在表达式(1)中到底是*p先被求值还是i++先被求值是由编译器决定的;两次副作用(对*p赋值以及i++)发生的顺序是由编译器决定的;甚至连子表达式i++的求值(就是初始时i的值)以及副作用(将i增加1)都不需要同步发生,编译器可以先用初始时i的值(即子表达式i++的值)对*p赋值,然后再将i增加1,这样就把子表达式i++的整个计算过程分成了两个不相邻的步骤。而且通常编译器都是这么实现的,原因在于i++的求值过程同*p = i++是有区别的,对于单独的表达式i++,执行顺序一般是(假设不考虑inc指令):先将i加载到某个寄存器A(如果i是寄存器变量则此步骤可以跳过)、将寄存器A的值加1、将寄存器A的新值写回i的地址;对于*p = i++,如果要先完整的计算子表达式i++,由于i++表达式的值是i的旧值,因此还需要一个额外的寄存器B以及一条额外的指令来辅助*p = i++的执行,但是如果我们先将加载到A的值写回到*p,然后再执行对i增加1的指令,则只需要一个寄存器即可,这种做法在很多平台都有重要意义,因为寄存器的数目往往是有限的,特别是假如有人写出如下的语句:
extern int i, j, k,
x = (i++) + (j++) + (k++);
编译器可以先计算(i++) + (j++) + (k++)的值,然后再对i、j、k各自加1,最后将i、j、k、x写回内存,这比每次完整的执行完++语义效率要高。
3. 序列点对副作用的限制
C99和C++2003都有类似的如下规定
Between the previous and next sequence point a scalar object shall
have its stored value modified at most once by the evaluation of an
expression. Furthermore, the prior value shall be accessed only to
determine the value to be stored. The requirements of this paragraph
shall be met for each allowable ordering of the subexpressions of a
otherwise the behavior is undefined.
也就是说,在相邻的两个序列点之间,一个对象只允许被修改一次,而且如果一个对象被修改则在这两个序列点之间对该变量的读取的唯一目的只能是为了确定该对象的新值(例如i++,需要先读取i的值以确定i的新值是旧值+1)。特别的,标准要求任意可能的执行顺序都必须满足该条件,否则代码将是undefined behavior
之所以序列点会对副作用有如此的限制,就是因为C/C++标准没有规定子表达式求值以及副作用发生之间的顺序,例如:
extern int i, a[];
extern int foo(int, int);
i = ++i + 1;
// 该表达式对i所做的两次修改都需要写回对象,i的最终值取决
// 于到底哪次写回最后发生,如果赋值动作最后写回,则i的值
// 是i的旧值Ĥ如果++i动作最后写回,则i的值是旧值Ĥ
// 因此该表达式的行为是undefined
// 如果=左边的表达式先求值并且i++的副作用被完成,则右边的
// 值是i的旧值Ĥ如果i++的副作用最后完成,则右边的值是i
// 的旧值,这也导致了不确定的结果,因此该表达式的行为将是
// undefined
foo(foo(0, i++), i++);
// 对于函数调用而言,标准没有规定函数参数的求值
// 顺序,但是标准规定所有参数求值完毕进入函数体
// 执行之前有一个序列点,因此这个表达式有两种执
// 行方式,一种是先求值外层foo调用的i++然后求值
// foo(0, i++),然后进入到foo(0, i++)执行,这之
// 前有个序列点,这种执行方式还是在两个相邻序列
// 点之间修改了i两次,undefined
// 另一种执行方式是先求值foo(0, i++),由于这里
// 有一个序列点,随后的第二个i++求值是在新序列
// 点之后,因此不算是两个相邻的序列点之间修改i
// 两次
// 但是,前面已经指出标准规定任意可能的执行路径
// 都必须满足条件才是定义好的行为,这种代码仍然
// 是undefined
前面我提到在一个序列点上程序的状态不一定是确定的,原因就在于相邻的两个序列点之间可能会发生多个副作用,这些副作用的发生顺序是未指定的,如果多于一个的副作用用于修改同一个对象,例如示例代码i = ++i + 1;,则程序的结果是依赖于副作用发生顺序的;另外,如果某个表达式既修改了某个对象又需要读取该对象的值,且读取对象的值并不用于确定对象新值,则读取和修改两个动作的先后顺序也会导致程序的状态不能唯一确定所幸的是,“在相邻的两个序列点之间,一个对象只允许被修改一次,而且如果一个对象被修改则在这两个序列点之间只能为了确定该对象的新值而读一次”这一强制规定保证了符合要求的程序在任何一个序列点位置上其状态都可以确定下来
注,由于对于UDT类型存在operator重载,函数语义会提供新的序列点,因此某些对于built-in类型是undefined behavior的表达式对于UDT确可能是良好定义的,例如:
// 如果i是built-in类型对象,则该表达式在两个相邻的序列点之间对
// i修改了两次,undefined
// 如果i是UDT类型该表达式也许是i.operator=(i.operator++(int)),
// 函数参数求值完毕后会有一个序列点,因此该表达式并没有在两个
// 相邻的序列点之间修改i两次,OK
由此可见,常见的问题如printf("%d, %d", i++, i++)这种写法是错误的,这类问题作为笔试题或者面试题是没有任何意义的.
类似的问题同样发生在cout && i++ && i++这种写法上,如果overload resolution选择成员函数operator&&,则等价于(cout.operator&&(i++)).operator&&(i++),否则等价于operator&&(operator&&(cout, i++), i++),如果i是built-in类型对象,这种写法跟foo(foo(0, i++), i++)的问题一致,都是未定义行为,因为存在某条执行路径使得i会在两个相邻的序列点之间被修改两次;如果i是UDT则该写法是良好定义的,跟i = i++一样,但是这种写法也是不推荐的,因为标准对于函数参数的求值顺序是unspecified,因此哪个i++先计算是不能预计的,这仍旧会带来移植性的问题,这种写法应该避免.
4. 编译器的跨序列点优化
根据前述讨论可知,在同一个表达式内对于同一个变量i,允许的行为是
A. 不读取,改写一次,例如
B. 读取一次或者多次,改写一次,但所有读取仅仅用于决定改写后的新值,例如
i = i + 1;
// 读取一次,改写一次
i = i & (i - 1);
// 读取两次,改写一次,感谢puke给出的例子
C. 不改写,读取一次或者多次,例如
j = i & (i - 1);
对于情况B和C,编译器是有一定的优化权利的,它可以只读取一次变量的值然后直接使用该值多次
但是,当该变量是volatile-qualified类型时编译器允许的行为究竟如何目前还没有找到明确的答案,ctrlz认为如果在两个相邻序列点之间读取同一个volatile-qualified类型对象多次仍旧是undefined behavior,原因在于该读取动作有副作用且该副作用等价于修改该对象,RoachCock的意见是两个相邻的序列点之间读取同一个volatile-qualified类型应该是合法的,但是不能被优化成只读一次。一段在嵌入式开发中很常见的代码示例如下:
if (i != i) {
// 探测很短的时间内i是否发生了变化
如果i != i被优化为只读一次,则结果恒为false,故RoachCock认为编译器不能
够对volatile-qualified类型的变量做出只读一次的优化。ctrlz则认为这段代码
本身是不正确的,应该改写成
if (j != i) {
// 将对volatile-qualified类型变量的多次读取用序列点隔开
虽然尚不能确定volatile-qualified类型的变量在相邻两个序列点之间读取多次行为是否合法以及将如何优化(不管怎么样,对于volatile-qualified类型这种代码应该尽量避免),但是可以肯定的是,对于volatile-qualified类型的变量在跨序列点之后必须要重新读取,volatile就是用来阻止编译器做出跨序列点的过激优化的,而对于non-volatile-qualified类型的跨序列点多次读取则可能被优化成只读一次(直到某个语句或者函数对该变量发生了修改,在此之前编译器可以假定non-volatile-qualified类型的变量是不会变化的,因为目前的C/C++抽象机器模型是单线程的),例如
bool flag =
void foo() {
while (flag) {
如果编译器探测到foo()没有任何语句(包括foo()调用过的函数)对flag有过修改,则也许会把(2)优化成只在进入foo()的时候读一次flag的值而不是每次循环都读一次,这种跨序列点的优化很有可能导致死循环。但是这种代码在多线程编程中很常见,虽然foo()没有修改过flag,也许在另一个线程的某个函数调用中会修改flag以终止循环,为了避免这种跨序列点优化带来到错误,应该把flag声明为volatile bool,C++2003对volatile的说明如下:
[Note: volatile is a hint to the implementation to avoid aggressive
optimization involving the object because the value of the object
might be changed by means undetectable by an implementation. See 1.9
for detailed semantics. In general, the semantics of volatile are
intended to be the same in C++ as they are in C. ]
5. C99定义的序列点列表
— The call to a function, after the arguments have been evaluated.
— The end of the first operand of the following operators:
logical AND && ;
logical OR || ;
conditional ? ;
— The end of a full declarator:
— The end of a full expression:
the expression in an
the controlling expression of a selection statement (if or switch);
the controlling expression of a w
each of the expression
the expression in a return statement.
— Immediately before a library function returns.
— After the actions associated with each formatted input/output function
conversion specifier.
— Immediately before and immediately after each call to a comparison
function, and also between any call to a comparison function and any
movement of the objects passed as arguments to that call.
6. C++2003定义的序列点列表
所有C99定义的序列点同样是C++2003所定义的序列点,此外,C99只是规定库函数返回之后有一个序列点,并没有规定普通函数返回之后有一个序列点,而C++2003则特别指出,进入函数(function-entry)和退出函数(function-exit)各有一个序列点,即拷贝一个函数的返回值之后同样存在一个序列点。
需要特别说明的是,由于operator||、operator&&以及operator,可以重载,当它们使用函数语义的时候并不提供built-in operators所规定的那几个序列点,而仅仅只是在函数的所有参数求值后有一个序列点,此外函数语义也不支持||、&&的短路语义,这些变化很有可能会导致难以发觉的错误,因此一般不建议重载这几个运算符。
7. C++2003中两处关于lvalue的修改对序列点的影响
在C语言中,assignment operators的结果是non-lvalue,C++2003则将assignment operators的结果改成了lvalue,目前尚不清楚这一改动对于built-in类型有何意义,但是它却导致了很多在合法的C代码在目前的C++中是undefined behavior,例如:
i = j = 1;
由于(j = 1)的结果是lvalue,该结果作为给i赋值的右操作数,需要一个lvalue-to-rvalue conversion,这个conversion代表了一个读取语义,因此i = j = 1就是先将1赋值给j,然后读取j的值赋值给i,这个行为是undefined,因为标准规定两个相邻序列点之间的读取只能用于决定修改对象的新值,而不能发生在修改之后再读取。
由于C++2003规定assignment operators的结果是lvalue,因此下列在C99中非法的代码在C++2003中却是可以通过编译的
(i += 1) += 2;
显然按照C++2003的规定这个代码的行为是undefined,它在两个相邻的序列点之间修改了i两次
类似的问题同样发生在built-in类型的前缀++/&operators上,C++2003将前缀++/& 的结果从rvalue修改为lvalue,这甚至导致了下列代码也是undefined behavior:
同样是因为lvalue作为assignment operator的右操作数需要一个左值转换,该转换导致了一个读取动作且这个读取动作发生在修改对象之后
C++的这一改动显然是考虑不周的,导致了很多C语言的习惯写法都成了undefined hehavior,因此Andrew Koenig在1999年的时候就向C++标准委员会提交了一个建议要求为assignment operators增加新的序列点,但是到目前为止C++标准委员会都还没有就该问题达成一致意见,我将Andrew Koenig的提议附后,如果哪位有时间有兴趣,可以看看,不过不看也不会有任何损失.
Purusa@newsmth对于序列点的介绍
发信人: Purusa (木偶机器人), 信区: CPlusPlus
题: [FAQ]i=i++和时序点(Sequence Point)
发信站: BBS 水木清华站 (Sun Jun 22 09:58:22 2003)
副作用真是一个很头痛的问题。初学者头痛是因为它令人迷惑,老鸟头痛是因为它会冷不
防叮你一口,只要你稍微粗心一点。当然,老碰到问这样问题的人也让人头痛。
为了理解副作用带来的困难,必须要理解C++中的时序点(Sequence Point)的概念。根据I
SO中1.9节第7款的叙述,时序点是前一求值中所有副作用已经结束而下一求值
中任何副作用尚未开始的地方。具体地说,有下面四个地方:
1. 在每一个非子表达式结束的地方。用ISO14882中的术语,就是完全表达式(full-expre
ssion)结束的地方。比如一条表达式语句,函数调用的一个完整参数,if语句里的条件表
达式等等;
2. 在一个函数所有参数求值完以后并且在准备调用该函数之前;
3. 当一个函数返回的时候:就是在返回值已经被复制以后,而在运行该函数的任何外部之
4. 在下列表达式中的第一个表达式之后,假设这里用的都是内置的运算符而没有被重载:
expr1 && expr2
expr1 || expr2
expr1 ? expr2 : expr3
expr1, expr2
理解了时序点的概念以后,其他的问题就迎刃而解了。C++里说的是,如果在两个时序点之
间有两个或者多个副作用,那么这些副作用的时序是不定的。如果表达式的值依赖于这些
副作用间的顺序,那么表达式的值也就是不定的。
i=i++;显然就属于这种情况,如果i这里是基本类型的话。而在c.erase(i++)就是另外一种
情况,因为在i++之后调用c.erase之前是有一个时序点的。因此就没有二义性问题。
Author: Le Cao
HTML generated by org-mode TAG=7.01g in emacs 23经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”
m = 1; n = m+++m++;
最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5:
a = 4; cout && a++ &&
C++ 不是规定 && 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题?
要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变量(通过赋值、增量/减量操作等),什么时候从该变量能够取到新值?有人可能说,“这算什么问题!我修改了变量,再从这个变量取值,取到的当然是修改后的值!”其实事情并不这么简单。
C/C++ 语言是“基于表达式的语言”,所有计算(包括赋值)都在表达式里完成。“x = 1;”就是表达式“x = 1”后加表示语句结束的分号。要弄清程序的意义,首先要理解表达式的意义,也就是:1)表达式所确定的计算过程;2)它对环境(可以把环境看作当时可用的所有变量)的影响。
如果一个表达式(或子表达式)只计算出值而不改变环境,我们就说它是引用透明的,这种表达式早算晚算对其他计算没有影响(不改变计算的环境。当然,它的值可能受到其他计算的影响)。如果一个表达式不仅算出一个值,还修改了环境,就说这个表达式有副作用(因为它多做了额外的事)。a++ 就是有副作用的表达式。这些说法也适用于其他语言里的类似问题。
现在问题变成:如果C/C++ 程序里的某个表达式(部分)有副作用,这种副作用何时才能实际体现到使用中?为使问题更清楚,我们假定程序里有代码片段“…a[i]++ … a[j] …”,假定当时i与j的值恰好相等(a[i] 和a[j] 正好引用同一数组元素);假定a[i]++ 确实在a[j] 之前计算;再假定其间没有其他修改a[i] 的动作。在这些假定下,a[i]++ 对 a[i] 的修改能反映到 a[j] 的求值中吗?注意:由于 i 与 j 相等的问题无法静态判定,在目标代码里,这两个数组元素访问(对内存的访问)必然通过两段独立代码完成。现代计算机的计算都在寄存器里做,问题现在变成:在取 a[j] 值的代码执行之前,a[i] 更新的值是否已经被(从寄存器)保存到内存?如果了解语言在这方面的规定,这个问题的答案就清楚了。
程序语言通常都规定了执行中变量修改的最晚实现时刻(称为顺序点、序点或执行点)。程序执行中存在一系列顺序点(时刻),语言保证一旦执行到达一个顺序点,在此之前发生的所有修改(副作用)都必须实现(必须反应到随后对同一存储位置的访问中),在此之后的所有修改都还没有发生。在顺序点之间则没有任何保证。对C/C++ 语言这类允许表达式有副作用的语言,顺序点的概念特别重要。
现在上面问题的回答已经很清楚了:如果在a[i]++ 和a[j] 之间存在一个顺序点,那么就能保证a[j] 将取得修改之后的值;否则就不能保证。
C/C++语言定义(语言的参考手册)明确定义了顺序点的概念。顺序点位于:
每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return语句的表达式,以及条件、循环和switch语句的控制表达式(for头部有三个控制表达式);
运算符 &&、||、?: 和逗号运算符的第一个运算对象计算之后;
函数调用中对所有实际参数和函数名表达式(需要调用的函数也可能通过表达式描述)的求值完成之后(进入函数体之前)。
假设时刻ti和ti+1是前后相继的两个顺序点,到了ti+1,任何C/C++ 系统(VC、BC等都是C/C++系统)都必须实现ti之后发生的所有副作用。当然它们也可以不等到时刻ti+1,完全可以选择在时段 [t, ti+1] 之间的任何时刻实现在此期间出现的副作用,因为C/C++ 语言允许这些选择。
前面讨论中假定了a[i]++ 在a[i] 之前做。在一个程序片段里a[i]++ 究竟是否先做,还与它所在的表达式确定的计算过程有关。我们都熟悉C/C++ 语言有关优先级、结合性和括号的规定,而出现多个运算对象时的计算顺序却常常被人们忽略。看下面例子:
(a + b) * (c + d) fun(a++, b, a+5)
这里“*”的两个运算对象中哪个先算?fun及其三个参数按什么顺序计算?对第一个表达式,采用任何计算顺序都没关系,因为其中的子表达式都是引用透明的。第二个例子里的实参表达式出现了副作用,计算顺序就非常重要了。少数语言明确规定了运算对象的计算顺序(Java规定从左到右),C/C++ 则有意不予规定,既没有规定大多数二元运算的两个对象的计算顺序(除了&&、|| 和 ,),也没有规定函数参数和被调函数的计算顺序。在计算第二个表达式时,首先按照某种顺序算fun、a++、b和a+5,之后是顺序点,而后进入函数执行。
不少书籍在这些问题上有错(包括一些很流行的书)。例如说C/C++ 先算左边(或右边),或者说某个C/C++ 系统先计算某一边。这些说法都是错误的!一个C/C++ 系统可以永远先算左边或永远先算右边,也可以有时先算左边有时先算右边,或在同一表达式里有时先算左边有时先算右边。不同系统可能采用不同的顺序(因为都符合语言标准);同一系统的不同版本完全可以采用不同方式;同一版本在不同优化方式下,在不同位置都可能采用不同顺序。因为这些做法都符合语言规范。在这里还要注意顺序点的问题:即使某一边的表达式先算了,其副作用也可能没有反映到内存,因此对另一边的计算没有影响。
回到前面的例子:“谁知道下面C语句给n赋什么值?”
m = 1; n = m++ +m++;
正确回答是:不知道!语言没有规定它应该算出什么,结果完全依赖具体系统在具体上下文中的具体处理。其中牵涉到运算对象的求值顺序和变量修改的实现时刻问题。对于:
cout && a++ &&
我们知道它是
(cout.operator &&(a++)).operator && (a);
的简写。先看外层函数调用,这里需要算出所用函数(由加下划线的一段得到),还需要计算a的值。语言没有规定哪个先算。如果真的先算函数,这一计算中出现了另一次函数调用,在被调函数体执行前有一个顺序点,那时a++的副作用就会实现。如果是先算参数,求出a的值4,而后计算函数时的副作用当然不会改变它(这种情况下输出两个4)。当然,这些只是假设,实际应该说的是:这种东西根本不该写,讨论其效果没有意义。
有人可能说,为什么人们设计 C/C++时不把顺序规定清楚,免去这些麻烦?C/C++ 语言的做法完全是有意而为,其目的就是允许编译器采用任何求值顺序,使编译器在优化中可以根据需要调整实现表达式求值的指令序列,以得到效率更高的代码。像Java那样严格规定表达式的求值顺序和效果,不仅限制了语言的实现方式,还要求更频繁的内存访问(以实现副作用),这些可能带来可观的效率损失。应该说,在这个问题上,C/C++和Java的选择都贯彻了它们各自的设计原则,各有所获(C/C++ 潜在的效率,Java更清晰的程序行为),当然也都有所失。还应该指出,大部分程序设计语言实际上都采用了类似C/C++的规定。
讨论了这么多,应该得到什么结论呢?C/C++ 语言的规定告诉我们,任何依赖于特定计算顺序、依赖于在顺序点之间实现修改效果的表达式,其结果都没有保证。程序设计中应该贯彻的规则是:如果在任何“完整表达式”(形成一段由顺序点结束的计算)里存在对同一“变量”的多个引用,那么表达式里就不应该出现对这一“变量”的副作用。否则就不能保证得到预期结果。注意:这里的问题不是在某个系统里试一试的问题,因为我们不可能试验所有可能的表达式组合形式以及所有可能的上下文。这里讨论的是语言,而不是某个实现。总而言之,绝不要写这种表达式,否则我们或早或晚会某种环境中遇到麻烦。
后记:去年参加一个学术会议,看到有同行写文章讨论某个C系统里表达式究竟按什么顺序求值,并总结出一些“规律”。从讨论中了解到某“程序员水平考试”出了这类题目。这使我感到很不安。今年给一个教师学习班讲课,发现许多专业课教师也对这一基本问题也不甚明了,更觉得问题确实严重。因此整理出这篇短文供大家参考。
转载自海同网校
总时间限制: 1000ms内存限制: 65536kB描述The objective of the program you are going to produce is to evaluate boo...
在C++里,表达式求值顺序一直是一个大坑,这是由于为了给编译器更大的优化空间,C++对表达式的求值做了许多非常灵活的规定(其实就是不规定,编译器愿意怎么实现都可以)。这些灵活的规定也给C++带来了许多...
c语言没有明确规定表达式的求值顺序!除了逻辑表达式的短路求值! 例如下面的程序:int a = 10;int func(){
return 5;}a
+ func()的值就不...
问题:算数运算的表达式求知
这道题不难,但是当你认真去编代码的时候,还是要考虑好多细节。
算法原理如下:
我们都知道算术四则运算的运算规则是:
先乘除,后加减。
从左到右计算
先算括号内,...
若一个表达式只求值,而为改变环境,就说该表达式是引用透明的(如cout
首先弄清楚一个问题:求值顺序并不是运算顺序
表达式求值是程序设计语言编译中的一个基本问题。它的实现就是对“栈”的典型应用。其实现思想和数据结构书上基本一致,不同的增加的函数计算,并可以扩充,利用两个栈:一个操作数栈和一个运算符栈。计算器C/C+...
经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”
m = 1; n = m+++m++;
最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出...
难得闲来无事,写一篇博客岂不是美滋滋。表达式求值的思路主要是将中缀表达式转换为后缀表达式,然后由后缀表达式进行求值,这里用到的数据结构主要是栈。中缀转后缀:遍历表达式,如果是数字,就直接输出,如果是操...
首先要了解前缀表达式,中缀表达式,后缀表达式
其实三者的区别用一句话就可概括,中缀表达式是给人算的,前缀,后缀表达式是给计算机计算的
它们都是对表达式的记法,因此也被称为前缀记法、中缀记法和后缀记...
没有更多推荐了,

我要回帖

更多关于 Y=AB+C的标准与或表达式为 的文章

 

随机推荐