i=i++引发的思考

简介:

博学,切问,近思--詹子知 (https://jameszhan.github.io)

在网上看到网友发的帖子,对于程序 int j=2,m=2;m+=(j++)+(++j)+(j++); 执行后结果有争议, 甚至在不同的语言环境下,它们执行的结果也截然不同。
其实,同类型的问题有很多,最出名就数i=i++了,在Java中,无论执行多少次这样的语句,i的值都不会改变,而在C/C++中却能够顺利的自加。究其根源,这跟他们编译后生成的字节码或机器代码的方式有关。在Java中,它的执行过程可以等价于:
int j = i; i = i + 1; i = j;

而在C/C++中,自加操作需要等到整个语句执行完才执行,执行过程是这样的:
int j = i; i = j; i = i + 1;

 

对于第一个例子,在java环境中,本段程序执行后的结果是i=5,m=12,而在C中执行的结果却是i=5,m=11。这让我搞了几年Java的人很是诧异。Java的思维习惯比较符合我们的习惯,对于语句中每个表达式分别求值,然后把结果相加。执行过程等价于:
a = j++; b = ++j; c = j++; m += a + b + c;
最后的结果是:m = m + a + b + c = 2 + 2 + 4 + 4 = 12, 而 i自加了3次,最后的值为5。

在c/c++中,i++和++i在语句中的执行是有很大差异的,i++运算符的意义是执行完当前语句之后,将目标值加1,而++i是在执行语句之前先完成自加操作。所以其执行过程等价于:
j++; a = j; b = j; c = j; m += a + b + c; j++; j++;
最后的结果是: m = m + a + b + c = 2 + 3 + 3 + 3 = 11, i同样自加了3次,最后值为5。

对知识的理解一定不能只停留于表象,如果有可能,尽量去索本求源,事实上,这段程序汇编后的代码也验证了c语言的规则。

main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $16, %esp movl $2, -12(%ebp) movl $2, -8(%ebp) addl $1, -8(%ebp) movl -8(%ebp), %eax addl -8(%ebp), %eax addl -8(%ebp), %eax addl %eax, -12(%ebp) addl $1, -8(%ebp) addl $1, -8(%ebp) addl $16, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret 

 

为了更好的验证这个规则,我们可以写一段更复杂一点的语句,看看他汇编后的程序是什么样子。

 

int m, i = 2; m = (++i) + (--i) + (i++) + (++i) + (i--) + (++i); 

这段程序,在Java中,m 的值是19,i 的值4. 执行的过程就是从前往后,每个表达式逐步执行,m=3+2+2+4+4+4 = 19。

 

而在C/C++中的执行过程是这样的,按照之前的规则,先做3次自加操作和一次自减操作,在把6个表达式的值相加,所以m=4+4+4+4+4+4=24,最后分别执行一次自加和自减操作。然而事实上,运行后的结果却是16。这又是为什么呢?我们先看一下,汇编后的代码。main: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $36, %esp movl $2, -12(%ebp) addl $1, -12(%ebp) subl $1, -12(%ebp) movl -12(%ebp), %eax addl -12(%ebp), %eax addl -12(%ebp), %eax addl $1, -12(%ebp) addl -12(%ebp), %eax addl -12(%ebp), %eax addl $1, -12(%ebp) addl -12(%ebp), %eax movl %eax, -8(%ebp) addl $1, -12(%ebp) subl $1, -12(%ebp)

 

 

用C代码还原是这样的:i++; i--; tmp += i; tmp += i; tmp += i; i++; tmp += i; tmp += i; i++; tmp += i; m = tmp; i++; i--;   ,可以看出m=2+2+2+3+3+4=16。很显然,在求值的过程中,++i的操作和求整个表达式值的操作交替执行了,但是i--和i++的操作还是等到求值完成后再去做的。

 

可见,对于上述表达式的值,我们是很难预期的,即使同是C,很有可能在不同的编译器环境下得到的结果也是不同的。避免的方法只有一个,不要写这种容易引起混淆的代码,虽然多了几行代码,但是却可以让人看的更明白,代码不是写给计算机的,而是写给后来人看的,所以为了节省自己和他人的时间,请规范你自己的代码。

目录
相关文章
|
编解码 开发工具 UED
QT Widgets模块源码解析与实践
【9月更文挑战第20天】Qt Widgets 模块是 Qt 开发中至关重要的部分,提供了丰富的 GUI 组件,如按钮、文本框等,并支持布局管理、事件处理和窗口管理。这些组件基于信号与槽机制,实现灵活交互。通过对源码的解析及实践应用,可深入了解其类结构、布局管理和事件处理机制,掌握创建复杂 UI 界面的方法,提升开发效率和用户体验。
501 13
|
SQL JSON 分布式计算
【赵渝强老师】Spark SQL的数据模型:DataFrame
本文介绍了在Spark SQL中创建DataFrame的三种方法。首先,通过定义case class来创建表结构,然后将CSV文件读入RDD并关联Schema生成DataFrame。其次,使用StructType定义表结构,同样将CSV文件读入RDD并转换为Row对象后创建DataFrame。最后,直接加载带有格式的数据文件(如JSON),通过读取文件内容直接创建DataFrame。每种方法都包含详细的代码示例和解释。
286 0
|
Go
Golang语言基础数据类型之复数complex
这篇文章介绍了Go语言中复数(complex)数据类型的概念、表示方法和使用示例,包括complex64和complex128两种类型的复数及其在不同领域的应用场景。
409 5
|
开发框架 JavaScript 前端开发
Angular 与 Ionic 简直太牛啦!双剑合璧构建高性能移动应用,开启跨平台开发新征程!
【8月更文挑战第31天】Angular是由Google维护的前端开发框架,使用TypeScript提供组件化开发、依赖注入等功能,适合构建复杂Web应用。Ionic则是基于Angular和Cordova的开源移动应用框架,提供丰富的UI组件以实现跨平台移动应用的快速构建。结合使用Angular与Ionic不仅能够显著提升开发速度并简化流程,还能够保证应用在iOS、Android及Web等多个平台上的良好运行,同时两者都有成熟的社区支持与资源可供利用。为了开始使用这两款工具,开发者需先安装Node.js和npm,接着利用Angular CLI和Ionic CLI创建项目并进行开发工作。
289 1
|
监控 网络协议 安全
2023年最新整理的中兴设备命令合集,网络工程师收藏!
2023年最新整理的中兴设备命令合集,网络工程师收藏!
1150 0
断网了怎么办?那就快去体验【Microsoft Edge浏览器】提供的离线冲浪小游戏~娱乐一下
断网了怎么办?那就快去体验【Microsoft Edge浏览器】提供的离线冲浪小游戏~娱乐一下
403 0
|
传感器 人工智能 安全
AIGC的应用场景
AIGC的应用场景
1472 0
|
人工智能 JavaScript Java
IM场景的移动端UI自动化测试平台实践
市面上的UI自动化平台基本上都是大同小异,把查找元素的方法抽象到一个下拉列表,再通过输入框输入要查找元素ID,查到到元素对应做一些动作。今天以opendx为例介绍一下UI自动化平台能力(它的页面和架构相对更人性化)。
879 0
|
机器学习/深度学习 文字识别 算法
基于Opencv实现车牌图片识别系统
• 这是一个基于spring boot + maven + opencv 实现的图像识别及训练的Demo项目 • 包含车牌识别、人脸识别等功能,贯穿样本处理、模型训练、图像处理、对象检测、对象识别等技术点 • java语言的深度学习项目,在整个开源社区来说都相对较少; • 拥有完整的训练过程、检测、识别过程的开源项目更是少之又少!!
558 0
基于Opencv实现车牌图片识别系统
|
存储 Kubernetes 容器
利用Helm简化Kubernetes应用部署
Helm 是由 Deis 发起的一个开源工具,有助于简化部署和管理 Kubernetes 应用。本文将介绍Helm的基本概念和使用方式,演示在阿里云的Kubenetes集群上利用 Helm 来部署应用。
40818 2
利用Helm简化Kubernetes应用部署