当前位置: 首页 > 编程日记 > 正文

有关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,之后是顺序点,而后进入函数执行。
不少书籍在这些问题上有错(包括一些很流行的书)。例如说C/C++ 先算左边(或右边),或者说某个C/C++ 系统先计算某一边。这些说法都是错误的!一个C/C++ 系统可以永远先算左边或永远先算右边,也可以有时先算左边有时先算右边,或在同一表达式里有时先算左边有时先算右边。不同系统可能采用不同的顺序(因为都符合语言标准);同一系统的不同版本完全可以采用不同方式;同一版本在不同优化方式下,在不同位置都可能采用不同顺序。因为这些做法都符合语言规范。在这里还要注意顺序点的问题:即使某一边的表达式先算了,其副作用也可能没有反映到内存,因此对另一边的计算没有影响。
回到前面的例子:“谁知道下面C语句给n赋什么值?”
m = 1; n = m++ +m++;
正确回答是:不知道!语言没有规定它应该算出什么,结果完全依赖具体系统在具体上下文中的具体处理。其中牵涉到运算对象的求值顺序和变量修改的实现时刻问题。对于:
cout << a++ << 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系统里表达式究竟按什么顺序求值,并总结出一些“规律”。从讨论中了解到某“程序员水平考试”出了这类题目。这使我感到很不安。今年给一个教师学习班讲课,发现许多专业课教师也对这一基本问题也不甚明了,更觉得问题确实严重。因此整理出这篇短文供大家参考。
后后记:4年多过去了,许多新的和老的教科书仍然在不厌其烦地讨论在C语言里原本并无意义的问题(如本文所指出的)。希望学习和使用C语言的人不要陷入其中。

转载于:https://www.cnblogs.com/yzl050819/p/6734051.html

相关文章:

HDU 3001

题目中说明每个城市至少要走一次&#xff0c;至多走2次&#xff0c;因此要用到三进制压缩&#xff0c;然后就是状态转移方程了。 这道题就处理三进制的地方麻烦一点。同时注意&#xff0c;在选择最小长度时&#xff0c;一定是要每一个点都经过至少一次的&#xff0c;即是状态的…

微信小程序 侧滑效果实现

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 先看效果图&#xff1a; 源码&#xff1a; <view wx:if{{if_show}} class{{show_centent?"show":"hide"}} /> <button bindtapbtn>展示 or 隐藏&l…

im和音视频开发哪个更好_如何阅读成为更好的开发者的方式

im和音视频开发哪个更好by nolan grace通过诺兰格雷斯 如何阅读成为更好的开发者的方式 (How to read your way to becoming a better developer) If you want to get better at programming, there are two things you need to do:如果您想提高编程水平&#xff0c;则需要做两…

MVC3学习 四 EF删除操作

由于EF的框架是4.1的&#xff0c;所以现在如果想更新部分字段的话&#xff0c;只能从数据库中查出一次数据&#xff08;不用查的方法还没找到&#xff0c;需要继续研究&#xff09;&#xff0c;不能像5.1的版本可以不用查。 更新的Action需要用到[HttpGet]和[HttpPost]&#xf…

ThinkPHP5.0中Redis的使用和封装(原创)

Redis是一种常用的非关系型数据库,主要用作数据缓存,数据保存形式为key-value,键值相互映射.它的数据存储跟MySQL不同,它数据存储在内存之中,所以数据读取相对而言很快,用来做高并发非常不错. ThinkPhP5.0自带了Redis扩展,在使用之前先下载php_redis.dll 网址 http://windows.p…

【微信小程序之画布】四:手指触摸绘波浪线

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 功能&#xff1a;根据手指触摸绘画一条直线路径--> 起点为手指开始触摸时的坐标&#xff0c;终点为手指触摸结束时的坐标 效果图&#xff1a; 上代码&#xff1a; <canvas clas…

saas的计费数据库设计_如何构建和扩展SaaS计费解决方案

saas的计费数据库设计您需要的最低可行产品 (What you need for a Minimum Viable Product) When you are building your Software as a Service (Saas) Minimum Viable Product (MVP), there is a lot of work that needs to be done. It can be difficult to balance this wo…

关于一对多,多对多的多表查询的控制

一、一对多 以班级Classes和学生Student为例&#xff1a;回忆sql语句://内链接,两种方式效果一样,查询的是两边都有的数据SELECT c.*,s.* FROM classes c,student s WHERE s.cidc.cid;SELECT c.cname,s.sname FROM classes c INNER JOIN student s ON s.cidc.cid;//左外连接&am…

JavaScript对象,方括号和算法

by Dmitri Grabov德米特里格拉波夫(Dmitri Grabov) JavaScript对象&#xff0c;方括号和算法 (JavaScript Objects, Square Brackets and Algorithms) One of the most powerful aspects of JavaScript is being able to dynamically refer to properties of objects. In this…

《Java 8 实战》(二)—— Lambda

Lambda表达式可以理解为简洁地表示可传递的匿名函数的一种方式&#xff1a;它没有名称&#xff0c;但它有参数列表/函数主体/返回类型&#xff0c;可能还有一个可以抛出的异常列表。 Lambda表达式由参数/箭头和主体组成&#xff1a; (Apple a1, Apple a2) -> a1.getWeight(…

c++回调函数 callback

&#xff08;1&#xff09;Callback方式Callback的本质是设置一个函数指针进去&#xff0c;然后在需要需要触发某个事件时调用该方法, 比如Windows的窗口消息处理函数就是这种类型。比如下面的示例代码&#xff0c;我们在Download完成时需要触发一个通知外面的事件&#xff1a;…

【微信小程序之画布】终:手指触摸画板实现

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 先看效果图&#xff1a; wxml <!--pages/shouxieban/shouxieban.wxml--> <view class"container"><view>手写板&#xff08;请在下方区域手写内容&…

Android开发中应避免的重大错误

by Varun Barad由Varun Barad Android开发中应避免的重大错误 (Critical mistakes to avoid in Android development) As many pioneers and leaders in different fields have paraphrased:正如许多不同领域的开拓者和领导人所说&#xff1a; In any endeavor, it is import…

机房收费系统(VB.NET)——超具体的报表制作过程

之前做机房收费系统用的报表是GridReport&#xff0c;这次VB.NET重构中用到了VisualStudio自带的报表控件。刚開始当然对这块功能非常不熟悉&#xff0c;只是探究了一段时间后还是把它做出来了。 以下把在VisualStudio&#xff08;我用的是VisualStudio2013&#xff0c;假设与您…

微信小程序实现画布自适应各种手机尺寸

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 解决的问题&#xff1a; 画布&#xff0c;动画等js里面的操作&#xff0c;默认是px而不是rpx, 无法根据手机屏幕自适应 达到的效果&#xff1a; 让画布&#xff0c;动画在不同分辨…

网易新闻首页实现

http://www.2cto.com/kf/201409/330299.html IOS后台运行机制详解&#xff08;二&#xff09; http://blog.csdn.net/enuola/article/details/9148691转载于:https://www.cnblogs.com/itlover2013/p/4403061.html

阿联酋gitex_航空公司网站不在乎您的隐私后续行动:阿联酋航空以以下方式回应我的文章:...

阿联酋gitexby Konark Modi通过Konark Modi 航空公司网站不在乎您的隐私后续行动&#xff1a;阿联酋航空对我的文章进行了全面否认 (Airline websites don’t care about your privacy follow-up: Emirates responds to my article with full-on denial) Yesterday, The Regis…

微信小程序把缓存的数组动态渲染到页面

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 代码实现的目的&#xff1a;当页面销毁的时候&#xff0c;页面的参数状态还是能够保存。 show_img函数实现&#xff1a; 创建一个数组保存到缓存&#xff0c;遍历缓存的list_stutas对…

Find Minimumd in Rotated Sorted Array

二分搜索查最小数&#xff0c;from mid to分别为区间的第一个&#xff0c;中位数&#xff0c;和最后一个数 if(from<mid&&mid<to)//顺序&#xff0c;第一个即为最小值 return from; if(from>mid)//发现逆序&#xff0c;则最小值在这个区间&#xff0c;2分搜索…

在DataTable中更新、删除数据

在DataTable中选择记录 /*在DataTable中选择记录*//* 向DataTable中插入记录如上&#xff0c;更新和删除如下:* ----但是在更新和删除前&#xff0c;首先要找出要更新和删除的记录。* 一种方法是遍历DataRow&#xff0c;搜索想要的记录&#xff0c;* --〉然而更聪明的办法是使用…

使用TensorFlow进行机器学习即服务

by Kirill Dubovikov通过基里尔杜博维科夫(Kirill Dubovikov) 使用TensorFlow进行机器学习即服务 (Machine Learning as a Service with TensorFlow) Imagine this: you’ve gotten aboard the AI Hype Train and decided to develop an app which will analyze the effective…

浏览器加载、解析、渲染的过程

最近在学习性能优化&#xff0c;学习了雅虎军规 &#xff0c;可是觉着有点云里雾里的&#xff0c;因为里面有些东西虽然自己也一直在使用&#xff0c;但是感觉不太明白所以然&#xff0c;比如减少DNS查询&#xff0c;css和js文件的顺序。所以就花了时间去了解浏览器的工作&…

《转》java设计模式--工厂方法模式(Factory Method)

本文转自&#xff1a;http://www.cnblogs.com/archimedes/p/java-factory-method-pattern.html 工厂方法模式&#xff08;别名&#xff1a;虚拟构造&#xff09; 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类…

微信小程序去除左上角返回的按钮

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 解决方法有两种&#xff1b; 1.把该页面设置为tab页面或者主页 ; 2.进入该页面使用 wx.reLaunch(); 示例 wx.reLaunch({url: ../detail/detail,}) 这样有一个弊端&#xff0c;就是…

我的第一个web_登陆我的第一个全栈Web开发人员职位

我的第一个webby Robert Cooper罗伯特库珀(Robert Cooper) 登陆我的第一个全栈Web开发人员职位 (Landing My First Full Stack Web Developer Job) This is the story of the steps I took to get my first job as a full stack web developer. I think it’s valuable to sha…

HTTP请求报文和HTTP响应报文(转)

原文地址&#xff1a;http://blog.csdn.net/zhangliang_571/article/details/23508953 HTTP报文是面向文本的&#xff0c;报文中的每一个字段都是一些ASCII码串&#xff0c;各个字段的长度是不确定的。HTTP有两类报文&#xff1a;请求报文和响应报文。 HTTP请求报文 一个HTTP请…

微信小程序用户未授权bug解决方法,微信小程序获取用户信息失败解决方法

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; bug示例图&#xff1a; 导致这个bug的原因是 wx.getUserInfo(OBJECT) 接口做了调整&#xff1b; 请看官方文档的描述&#xff1a; wx.getUserInfo(OBJECT) 注意&#xff1a;此接口有…

格式化json日期'/Date(-62135596800000)/'

日期经过json序列化之后&#xff0c;变成了/Date(-62135596800000)/字符串&#xff0c;在显示数据时&#xff0c;我们需要解释成正常的日期。 Insus.NET和js库中&#xff0c;写了一个jQuery扩展方法&#xff1a; $.extend({JsonDateParse: function (value) {if (value /Date(…

aws lambda使用_使用AWS Lambda安排Slack消息

aws lambda使用Migrating to serverless brings a lot of questions. How do you do some of the non-serverless tasks, such as a cronjob in a serverless application?迁移到无服务器带来了很多问题。 您如何执行一些非无服务器的任务&#xff0c;例如无服务器应用程序中的…

微信小程序模块化开发 include与模板开发 template

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 正文&#xff1a; 1. include 是引用整个wxml文件&#xff0c;我通常会配合js&#xff0c;css一起使用&#xff1b; 使用场景&#xff0c;需要封装事件和微信 api 的公共模块。 2.template &#xff…