Compiler- 自增运算

news2024/10/5 15:28:03

我们来看一下C语言中的前自增(++i)和后自增(i++) 这个经典案例。大家在学习C的时候肯定学过前自增是先自增,然后将结果用于计算;后自增是先参与计算,再增加。

好,看一下这段代码的结果:

#include <stdio.h>

int main()
{
    int i = 3;

    int sum;
    sum = i++ + ++i ;
    printf("sum is %d.\n",sum);
    
    return 0;
}

  • i ++, 相当于先从内存中把这个值取到寄存器,真正参与 "+" 这个运算的是直接从寄存器中取值参与运算。
  • ++ i,首先修改内存,参与运算的时候再把它从内存当中取到寄存器。
  • 然后两个寄存器参与 "+" 运算(sum = i++  +  ++i)

这个题目估计大家问题不大。那,下面的代码呢(手动滑稽hh~)?

#include <stdio.h>

int main()
{
    int i = 3;

    int sum;
    sum = ++i + ++i ;
    printf("sum is %d.\n",sum);
    
    return 0;
}

 这个输出结果笔者曾一度坚定的认为是9(就算天王老子来了,它也得是9!!!)。

啪啪打脸hh~,我不理解,为啥是10呢?我们下面就来好好分析一下


我们将这两个程序生成的可执行文件反汇编,对比看看有什么不同

下图为第一个程序的汇编代码 (sum = i++  +  ++i)

对上述汇编代码做详细解释: 

movl   $0x3,-0x8(%rbp)       将3移动到-0x8(%rbp)这个内存位置,相当于对变量i进行赋值(i = 3)

接下来要做的是i ++,

mov    -0x8(%rbp),%eax      首先把这个内存位置的值拷贝到eax寄存器中(%eax = 3)

lea    0x1(%rax),%edx          然后开始对寄存器eax中的值+1 赋到edx中 (%edx = 4)

mov    %edx,-0x8(%rbp)       接下来将edx中的值移动到内存中(i = 4)

接下来要做的是++ i,

addl   $0x1,-0x8(%rbp)         直接对内存位置的值+1(i = 5)

到此,i ++ 和 ++ i都已经计算结束,要开始进行加法运算了

左边的值已经保存在eax中了,

mov    -0x8(%rbp),%edx       现在要从内存中把i的值读出来放到另一个寄存器edx中(%edx = 5)

执行加法运算时,

add    %edx,%eax                 (%eax += %edx)此时eax的值就是8了

这个程序与我们分析的是一致的,那两个++ i又是什么情况呢?我们来分析一下

 (注:为了省事,我直接在图中标注了hh~)

看出来区别了吗?做加法首先要把操作数准备好。

对于i ++,首先做的是

115c:	8b 45 f8             	mov    -0x8(%rbp),%eax  // %eax = 3
115f:	8d 50 01             	lea    0x1(%rax),%edx   // %edx = 4
1162:	89 55 f8             	mov    %edx,-0x8(%rbp)  // i = 4

将 i 变量的值从内存(-0x8(%rbp))放入寄存器 %eax,然后在寄存器中增加1,将值放入%edx,然后再将%edx中的值放入内存(4)。这里%eax保存了原始的值3,是加法操作的一个操作数,同时内存的值更新。

对于++ i,是怎么做的呢?

1160:	83 45 f8 01          	addl   $0x1,-0x8(%rbp)    // i = 5

这里就比较简单粗暴,直接在内存位置进行了加1。

后面在进行加法操作的时候,左操作数已经准备好了(在%eax中),然后右操作数再从内存进入寄存器,此时的值就是5。所以3+5=8。这里要特别强调的一点,++ i 不是自己一遍做好就取出来存到寄存器,而是会等到所有的对 i 的操作都结束,在必须使用它来进行操作的时候才从内存中取值,所以它的值是 i 的最新值,而不一定是 ++ i之后的值。

为了说明这个问题,我们再来看一个例子。

#include <stdio.h>

int main(){
    
    int i = 3;

    int sum;
    sum = ++i + i++;
    printf("sum is %d.\n",sum);

    return 0;
}

大家觉得这个结果是多少呢?(答案是9)

++ i 之后变成4, i ++ 把这个i的值4取出来,放到寄存器中,然后i ++,i的值变成了5,再写回内存,当要进行加法操作的时候,就要取 i 的值,此时i的值为现在内存中的值,所以++ i 变成了5,而i ++ 是4,故sum = 5 + 4 = 9

还是看一下汇编代码比较清晰

0000000000001149 <main>:
    1149:	f3 0f 1e fa          	endbr64 
    114d:	55                   	push   %rbp
    114e:	48 89 e5             	mov    %rsp,%rbp
    1151:	48 83 ec 10          	sub    $0x10,%rsp
    1155:	c7 45 f8 03 00 00 00 	movl   $0x3,-0x8(%rbp)    // i = 3
    115c:	83 45 f8 01          	addl   $0x1,-0x8(%rbp)    // i = 4
    1160:	8b 45 f8             	mov    -0x8(%rbp),%eax    // %eax = 4
    1163:	8d 50 01             	lea    0x1(%rax),%edx     // %edx = 5
    1166:	89 55 f8             	mov    %edx,-0x8(%rbp)    // i = 5
    1169:	8b 55 f8             	mov    -0x8(%rbp),%edx    // %edx = 5
    116c:	01 d0                	add    %edx,%eax          // %eax = 9
    116e:	89 45 fc             	mov    %eax,-0x4(%rbp)    // sum = 9
    1171:	8b 45 fc             	mov    -0x4(%rbp),%eax
    1174:	89 c6                	mov    %eax,%esi
    1176:	48 8d 05 87 0e 00 00 	lea    0xe87(%rip),%rax        # 2004 <_IO_stdin_used+0x4>
    117d:	48 89 c7             	mov    %rax,%rdi
    1180:	b8 00 00 00 00       	mov    $0x0,%eax
    1185:	e8 c6 fe ff ff       	call   1050 <printf@plt>
    118a:	b8 00 00 00 00       	mov    $0x0,%eax
    118f:	c9                   	leave  
    1190:	c3                   	ret   

从这个例子,我们可以看出来什么呢?语言的特点其实是有编译的实现来决定的。比如,我们计算++ i 的时候,就是要先改它的值,并且参与计算的时候,我一定要取到它的最新值。

【注意与i ++的区别,一个是取它原来的值(i ++),一个是取它最新的值(++ i)】

我们学习语言的时候如果主动地去想一想底层的特点,那么对语言特性的理解会更深刻。如果能真正理解编译器怎么处理++i,那么对上面例子的结果应该也能理解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/458016.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PE文件反编译为python脚本流程

1、查壳 DetectltEasy、PeiD查壳 2、脱壳 常见打包工具PyInstaller&#xff0c;脱壳方法 &#xff08;1&#xff09;用pyinstxtractor.py脱壳&#xff0c;用”python pyinstxtractor.py 1.exe“命令&#xff0c;生成“.exe文件名_extracted” &#xff08;2&#xff09;用…

Python+Qt人脸识别门禁管理系统

程序示例精选 PythonQt人脸识别门禁管理系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonQt人脸识别门禁管理系统>>编写代码&#xff0c;代码整洁&#xff0c;规则&am…

1 ROS2介绍与安装

1 ROS2介绍与安装 1.1 Ubuntu配置与ROS2安装1.1.1 Ubuntu22.04安装1.1.2 下载安装ROS21.1.3 配置ROS2环境并测试 1.2 使用VSCode搭建ROS2开发环境1.2.1 安装并配置VSCode1.2.2 创建ROS2工程的方法1.2.3 使用VSCode创建ROS2的C/C项目1.2.4 使用VSCode创建ROS2的Python项目 1.3 R…

Samba配置回收站功能

部门确实需要给Samba配置回收站&#xff0c;查阅了下回收站的资料&#xff0c;配置也挺简单的。 配置说明&#xff1a; 在Samba配置回收站功能中各参数作用如下。 (1) vfs object recycle&#xff1a;载入Samba用于回收站功能的模块recycle.so。 (2) recycle:repository /Pr…

企业如何保护外发文件的数据安全?

随着数字化转变&#xff0c;企业的业务文件大多通过电子形式在内外部流转。这增加了外发文件数据泄露或被篡改的风险&#xff0c;如何保护外发文件安全已成为企业不容忽视的课题。 企业外发文件&#xff0c;特别是电子文件&#xff0c;存在一定的数据安全风险&#xff1a; 文件…

第十二章 外观模式

文章目录 前言一、外观模式基本介绍完整代码DVD类爆米花类投影仪类屏幕类立体声类灯光类家庭影院类进行聚合Client测试类 二、 外观模式在MyBatis框架应用的源码分析三、外观模式的注意事项和细节 前言 一、外观模式基本介绍 完整代码 DVD类 package tanchishell.SJMS.faca…

Layui 2.8.0 正式发布,朴实归来

Layui 是一套开源的 Web UI 组件库&#xff0c;采用自身轻量级模块化规范&#xff0c;遵循原生态的 HTML/CSS/JavaScript 开发模式&#xff0c;极易上手&#xff0c;拿来即用。其风格简约轻盈&#xff0c;而内在雅致丰盈&#xff0c;甚至包括文档在内的每一处细节都经过精心雕琢…

On the Efficacy of Knowledge Distillation 解析

paper&#xff1a;On the Efficacy of Knowledge Distillation 本文的题目是《论知识蒸馏的有效性》&#xff0c;主要是对教师模型并不是越大越好这一现象进行研究&#xff0c;并提出了缓解方法&#xff1a;early stop。 Bigger models are not better teachers 知识蒸馏背…

S32k3系列开发学习(FlexCAN)

前言 由于之前没有接触过CAN总线模块&#xff0c;对这一块的知识仍比较陌生&#xff0c;于是乎想简单梳理一下CAN总线的工作流程&#xff0c;加深理解。 一、CAN是什么&#xff1f; 参考&#xff1a;https://zhuanlan.zhihu.com/p/346696648 二、CAN框架 各模块功能如下&am…

NLP基础:标注器Label Studio的入门使用

目录 一、环境准备 二、操作 文章来源&#xff1a; 简介&#xff1a; Label Studio是一个开源的数据标注工具&#xff0c;它可以用于各种机器学习和深度学习项目。它的主要目的是帮助数据科学家和机器学习工程师快速、高效地标注数据&#xff0c;以构建和训练准确的机器学…

Linux入门---开发的Linux命令手册

Linux 基础知识 基础 启动过程&#xff1a; 内核的引导。运行 init。系统初始化。建立终端 。用户登录系统。 命令介绍 磁盘 #文件#a&#xff1a;相当於 -pdr 的意思&#xff0c;至於 pdr 请参考下列说明&#xff1b;(常用)&#xff1b;f&#xff1a;为强制(force)进行&…

Ubuntu20.04使用多卡训练HyperNetwork模型和LoRA模型全流程及疑难问题解决方案

目录 一. LoRA模型多卡训练1.1 安装xformer等库1.2 设置路径1.3 多卡训练 二. LoRA模型多卡训练疑难报错解决方案多卡训练报错 软硬件配置&#xff1a; CPU: AMD 5800 8core 16Thread GPU: NVIDIA RTX 3090 *1 NVIDIA TITAN RTX *1 OS: Ubuntu20.04 一. LoRA模型多卡训练 1.1 …

JavaScript概述四(DOM文档对象模型)

1.DOM(Document Object Model) 会把网页里面的元素当成对象去操作,包含对象的属性,属性值,方便我们去 操作网页。 整个页面最终会形成一个对象 :document ,页面里面的所有的元素(如 标签 ) 最终都会转换成 js 里面的对象。 1.1 获取页面的元素&#xff08;通过选择器&#xff0…

S32K3系列单片机学习

前言 定时器中断&#xff0c;每个平台的实现方式均有差异&#xff0c;从51单片机通过寄存器配置实现定时器周期计数&#xff0c;再到使用HAL库配置STM32的定时器&#xff0c;他们的实现原理都是大同小异的&#xff0c;只不过不同的平台使用的底层库不同&#xff0c;导致实现的…

公司25k招了一个测试员不会自动化,试用期没过就赶走了...

最近翻了一些网站的招聘信息&#xff0c;把一线大厂和大型互联网公司看了个遍&#xff0c;发现市场还是挺火热的&#xff0c;虽说铜三铁四&#xff0c;但是软件测试岗位并没有削减多少&#xff0c;建议大家有空还是多关注和多投简历&#xff0c;不要闭门造车&#xff0c;错过好…

手推FlinkML2.2(一)

Java 快速入门 # 本文档提供了一个关于如何使用Flink ML的快速入门。阅读本文档的用户将被指导提交一个简单的Flink作业&#xff0c;用于训练机器学习模型并提供预测服务。 求助&#xff0c;我卡住了&#xff01;# 如果你遇到困难&#xff0c;请查看社区支持资源。特别是&…

第一个 Servlet 程序

1. 创建项目 使用 IDEA 创建一个 Maven 项目. 1.1、File -> New Project Name:javaservlet2 Location:选择要存放的路径 Language:Java Build system:Maven 点击Create按钮 1.2、Pom.xml引入依赖 依赖包来源&#xff1a; <dependencies> <!-- https…

【Hive实战】探索Hive 2.X以及更早版本的MetaStore

探索Hive 2.X以及更早版本的MetaStore 文章目录 探索Hive 2.X以及更早版本的MetaStore概述配置元数据服务和元数据存储库基础配置参数其他配置参数默认配置配置元服务数据库使用内嵌模式的Derby库使用远程数据存储库 配置元数据服务本地/内嵌服务配置远程服务配置 元数据服务配…

Java Collection源码分析(JDk corretto 11)

文章目录 Collection 系列源码分析 (JDK Amazon corretto 11)Collection接口Iterable接口 子接口 QueueQueue的子接口 Deque双端队列 子接口ListArrayList 实现类序列化与反序列化(后续解决)获取Calss对象的方式 主要有三种&#xff1a;Arrays工具类System类 LinkedList实现类t…

kotlin的let,with,run,apply,also,异同区别

kotlin的let&#xff0c;with&#xff0c;run&#xff0c;apply&#xff0c;also&#xff0c;异同区别 例如&#xff1a; class Person(var name: String, var age: Int) {fun eat() {println("吃饭")}fun work(hour: Int): Int {println("$name $age 工作 $ho…