C/C++表达式求值顺序的过分讨论原来是没有必要的

C++实现简单表达式求值 【代码】C++实现简单表达式求值。 阅读详情
裘宗燕: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语言的人不要陷入其中。
关于C/C++ 表达式求值顺序   表达式求值顺序不同于运算结合性和优先级。下面是一个经典例子,被 ISO C99/ C++98 /03 三大标准明确提到:他的结果是不确定(unspecified) 的。 i = ++i + 1;   // The behavior is unspecified     在介绍概念之前,我们先解释一下它的结果。这个表达式( expression )包含3个子表达式( subexp 阅读详情

相关推荐

RDP Wrapper失效了?别慌!教你如何手动为Windows 10/11最新版本添加rdpwrap.ini配置

本文详细介绍了当RDP Wrapper在Windows 10/11最新版本中失效时,如何手动添加rdpwrap.ini配置以恢复多用户远程桌面功能。通过获取最新配置文件、安全替换配置的完整流程以及高级手动配置技巧,帮助用户解决[Not support]问题,确保远程工作流程不受系统更新影响。

weixin_30765577的博客 624

【C++】表达式求值

表达式求值

高朗的博客 1801

谷粒商城篇章9 ---- P248-P261/P292-P294 ---- 消息队列【分布式高级篇六】

RabbitMQ消息队列、RabbitMQ延时队列。

weixin_48568302的博客 1550

C++知识点总结(25):表达式求值

一行,为需要你计算的表达式,表达式中只包含数字、四则运算符号(除号表示整除)和点号(点号表示出现新的数字)以及等于号(代表式子输入结束)。一行,为需要你计算的表达式,表达式中只包含数字、加法运算符 “给定一个包含加减乘除的算术表达式,请你编程计算表达式的值。由数字、运算符、括号等组成,能求得数值的组合。” ,且没有括号,所有参与运算的数字均为。运算符再两个操作数之前的表达式。一个整数,表示这个表达式的值。一个整数,表示这个表达式的值。表达式中的除法保证能够整除。转前缀表达式的结果是?运算符在中间的表达式。

joe_g12345的博客 2555

07群晖Docker部署nousresearch/hermes-agent

群晖Docker部署nousresearch/hermes-agent。

分享技术,传播知识! 1382

Nvidia Jetson CH340 驱动安装

最近调一个USB设备,Windows系统上是可以安装CH340 驱动使用的,移到Jetson AGX Orin 上发现没有 /dev/ttyUSB* 设备显示。是一个为盲人和视力障碍者提供的终端设备驱动程序,它会在系统启动时自动加载并占用串口设备,导致我们无法正常使用部分串口设备。安装完后,内核中已经有。

Vulcan_S的博客 1639

表达式求值C++实现

表达式求值这个知识点在最近几年的找工作笔试、面试,考研机试,各种算法竞赛笔试中出现的频率越来越高了;但是以前从来没有见过这种题要想在笔试面试中写出来不是一件简单的事情,网络上面大部分代码不够精炼,不方便理解和背诵模板;本篇博客提供了表达式求值的一道经典模板题,从理解表达式求值思路到整个代码模板,方便大家理解和背诵;旨在帮助广大笔试面试的朋友轻松应对此类问题。

PengHao666999的博客 5549

【系统分析师】高分论文:论软件需求验证方法及应用

本文以某清算平台建设项目为例,探讨了软件需求验证方法的应用。该项目旨在隔离银行与支付公司的直接通信,满足央行监管要求。作为系统分析师,作者通过需求测试和需求评审两种方法确保需求质量。需求测试采用概念测试用例提前发现问题;需求评审通过正式与非正式形式多方确认需求。项目结合邮件评审、专家会议等方式,最终形成完善的需求文档。平台上线后运行良好,验证了需求管理的重要性。该案例表明,科学的需求验证是项目成功的关键保障。

数据知道的博客 8125

【c++入门(2)】表达式求值

一、前中后缀表达式 二、表达式计算 前中后缀表达式 (1)中缀表达式 含义:就是我们最常见的普通表达式,操作符在操作数的中间。 如: 3 + 5=? 3 + 5 + 6=? 3 + 5 - 2=? 3 + 6 * 2=? 3 + 6 / 2=? 3 * 6 + 2=? (3 + 6) * 2 (4 + 6) / 2 (3 + 6 * 4) / 2 有几个注意点:在计算的时候需要注意优先级,"()" > "*/" > "+-" (2.

1 2184

29.Labview界面设计 --- (下篇)自定义控件库、界面布局与外观设计

1.自定义控件外观(自定义控件)1.1 自定义外观库1.2 手动自定义控件外观2. 前面板控件布局方式(控件排布)2.1 模块化控制布局方式2.2 数据采集布局方式3. 前面板外观优化项(背景设计)3.1 前面板背景设计3.2 前面板窗口整理本节第一部分教大家安装一种可以直接用的自定义控件库,第二部分教大家如何自己设计控件并保存调用。

关注我,更多技术问题欢迎私聊~ 4783

掌握C++新特性中智能指针的综合指南

本文是一篇关于C++中智能指针的综合指南,旨在深入探讨现代内存管理的核心概念和技术。智能指针是C++中一种强大的工具,可以帮助开发人员更安全、高效地管理内存资源。通过详细解析各种类型的智能指针,包括unique_ptr、shared_ptr和weak_ptr,读者将了解它们的原理、用法和适用场景。本文将重点介绍智能指针的优势,如自动内存释放、避免内存泄漏和循环引用等问题。此外,还将讨论智能指针与传统指针的区别,并提供一些最佳实践和实用技巧,帮助读者在实际项目中正确、高效地使用智能指针。

Lion_Long的博客 1200

关于C/C++ 表达式求值顺序 未定义(转)

一篇很好的介绍求值次序的文章,具体链接未知。 表达式的副作用 运算符的优先级与结合性规定了表达式中相邻两个运算符的运算次序,但对于双目 运算的操作数,C++没有规定他们的计算次序。例如,对于表达式:     exp1 + exp2;   先计算 exp1 还是 exp2?不同的编译器有不同的做法。  在数学上,对于双目运算符,不论先计算

Pegasus (http://ningning.today) 2150

RK3568平台开发系列讲解(NPU篇)什么是RKNPU

📢本篇章将介绍RKNPU。

内核笔记 4354

C/C++ 语言中的表达式求值

经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout << a++ << a; C++ 不是规定 << 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题?...

qq_42577465的博客 2549

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

顺序栈使用——表达式求值

顺序栈使用——表达式求值 码文不易,如果帮助到您,希望您可以帮我刷一下点击量,与您无害,与我有益谢谢 支持原创 。   欢迎大家阅读我的博客,如果有错误请指正,有问题请提问,我会尽我全力改正错误回答问题。在次谢谢大家。下面开始正式内容   顺序栈部分在之前已经在另一篇文章中提及,本篇中就不再提及,如有需要请阅读&lt;顺序栈的各种基本运算&gt;。 实验环境 ...

Paradox 5370

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

C,C++表达式求值顺序 裘老的解释

核心观点: 1,有顺序点的概念:          程序执行中存在一系列顺序点(时刻),语言保证一旦执行到达一个顺序点, 在此之前发生的所有修改(副作用)都必须实现(必须反应到随后对同一存储位置的访问中), 在此之后的所有修改都还没有发生。 C/C++中顺序点有:          1>. 每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return语句的表达式,以及

huangxy10的专栏 1478

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

c语言表达式求值顺序,关于C/C++ 表达式求值顺序

关于C/C++ 表达式求值顺序导语:表达式求值顺序不同于运算结合性和优先级。下面是一个经典例子,被 ISO C99/ C++98 /03 三大标准明确提到:他的结果是不确定(unspecified) 的。 下面是关于C/C++ 表达式求值顺序,欢迎学习:i = ++i + 1; // The behavior is unspecified在介绍概念之前,我们先解释一下它的结果。这个表达式( exp...

weixin_39842955的博客 655

C,C++表达式求值顺序超详解

经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout  C++ 不是规定 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题?   要弄清这些,需要理解的一个问题是:如果程序里某处修改了一

iotekclass专栏 828

【C++17 core language features】C++17核心特性解析:更严格的表达式求值顺序

在C++中,**表达式求值顺序(Expression Evaluation Order)**指的是在一个复杂表达式中,各个子表达式被评估的顺序。理解和掌握表达式求值顺序对于编写高效、安全且行为可预测的C++代码至关重要。错误的求值顺序可能导致未定义行为(Undefined Behavior)、性能瓶颈甚至安全漏洞。

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 1246

裘宗燕:C/C++ 语言中的表达式求值顺序

经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout  C++ 不是规定  要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变量(通过赋值、增量/减量操作等),什么时候从该变量能够取到新值?

NightkidLi_911的专栏 1073

C,C++表达式中含++的求值顺序

文章目录前言一、提出问题二、思考三、总结四、后记 前言 经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout << a++ << a; C++ 不是规定 << 操作左结合吗?是C++ 书上写错了,还是这个系统的实现有问题? 要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变

weixin_44602646的博客 335

C/C++表达式求值问题

转载:https://originlee.com/2016/05/01/eval-expression-in-c-and-cpp/ 前几日,一个刚学编程的老朋友问了我一个问题: int i = 0;i = i ++;printf(“%d\n”, i); 为什么打印i的值是1而不是0? 这种undefined的问题在网上是讨论烂了的。一般会纠结这种问题的同学,多半是看了本烂书。 我...

weixin_33725126的博客 294

C/C++语言中的表达式求值

裘宗燕:C/C++ 语言中的表达式求值 经常可以在一些讨论组里看到下面的提问:“谁知道下面C语句给n赋什么值?” m = 1; n = m+++m++; 最近有位不相识的朋友发email给我,问为什么在某个C++系统里,下面表达式打印出两个4,而不是4和5: a = 4; cout  C++ 不是规定  要弄清这些,需要理解的一个问题是:如果程序里某处修改了一个变量(通过赋值、增量

Study_2011的专栏 783
上一篇: 复制二叉树(非递归实现)
下一篇: 数据库的范式:1NF,2NF,3NF
C++程序员Carea
博客等级 码龄18年 1590粉丝 878原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值