C_C++ 语言中的表达式求值.docx
文本预览下载声明
裘宗燕:C/C++?语言中的表达式求值经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?”m?=?1;?n?=?m+++m++;最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5:a?=?4;?cout??a++??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++语言定义(语言的参考手册)明确定义了顺序点的概念。顺序点位于:1.?每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return语句的表达式,以及条件、循环和switch语句的控制表达式(for头部有三个控制表达式);2.?运算符?、||、?:?和逗号运算符的第一个运算对象计算之后;3.?函数调用中对所有实际参数和函数名表达式(需要调用的函数也可能通过表达式描述)的求值完成之后(进入函数体之前)。假设时刻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,之后是顺序点,而后进入函数执行。不
显示全部