C和C++语言 ++i + ++i 等于多少

目录

分析

在gcc11.2编译器下,++i到底做了什么?

汇编分析

总结

++i + ++i 的完整分析

c + gcc

c++ + gcc

c + clang

全篇总结

扩展阅读


有说 ++i + ++i 等于5的,也有说等于6的,这两种结果也都有人跑出来过。

以我个人的拙见,其实两种都是对的,也都是可以跑出来的。(剧透一下,gcc是6,clang是5)

分析

下面是一点分析。

在gcc11.2编译器下,++i到底做了什么?

汇编指令总共有8行,其实真正核心的就2行,第4行和第5行。

int i = 1; mov [rbp-4], 1;

++i; add [rbp-4], 1;

汇编分析

这里不懂汇编也没有关系,我会逐一讲解,如果懂汇编,可以跳过

首先 mov 是右边的数据写入到左边,左边是容器右边是值(左边是篮子右边是鸡蛋)。

这里是将1写入 [rbp-4],这个 [rbp-4] 是什么东西?首先外面的中括号代表访问内存,具体访问内存的哪个地址,由中括号里面的值决定,也就是说,中括号里面的值就是内存地址

那这个 rbp 又是什么呢?rbp 是一个寄存器,寄存器是在CPU内部存储原件,数量稀少,十分滴珍贵。每一个寄存器都有特定用途和名字,rbp就是一个寄存器的名字,这个寄存器存储的是栈基址。当调用一个函数时,会在栈上为这个函数分配空间,当函数运行完毕时,会回收这部分空间,rbp指向就是函数栈的起始地址

那这里 rbp-4 如果按照访问数组的方式来理解,应该是 rbp+4 啊,为什么是 -4 呢?那是因为栈在内存里面从高地址向低地址增长,说人话就是,栈是倒过来的,什么叫倒过来呢?

正常定义一个 char数组 用来存储字符串,它在内存里面就是这样的,从0(低地址)开始到5(高地址)会存储数组的内容,0表示字符串结束。这个就称为低地址向高地址增长

 高地址向低地址增长就长这样,右边呢,是为了表示方便的画法,把高地址放在上面,低地址放在下面。

 

 通过以上信息,mov [rbp-4], 1; 实际操作后的函数栈内存

 add [rbp-4], 1;  这个就非常简单了,就是把 rbp-4 处的内存值加1,然后写回 rbp-4

总结

int i = 1;        就是在main函数栈中的一块内存写入1

++i;        就是将 i 指向的内存+1

++i + ++i 的完整分析

c + gcc

// int i = 1;
mov [rbp-4], 1    // i 变量的定义,其实就是把 1 写入 rbp-4 的内存,
                  // 并且在 main 函数内使用 i,在汇编都会替换为 [rbp-4]
// int k = 0;
mov [rbp-8], 0    // 这里和 i 变量定义道理是一样的,不再赘述

// k = ++i + ++i;
add [rbp-4], 1    // 将i指向的内存加 1,对应第一个 ++i
add [rbp-4], 1    // 将i指向的内存加 1,对应第二个 ++i
                  // 这时候,rbp-4 的内存区域,值是 3

mov eax, [rbp-4]  // 将 3 写入到 eax
add eax, eax      // 将 eax 加 eax ,结果写入到 eax,eax 就是 6
mov [rbp-8], eax  // 将 eax 写入到 k 变量内存

注意这是 C语言 + x86-64 gcc 11.2 的汇编结果。

c++ + gcc

 c++语言 和 c语言 在 gcc编译器下几乎没有差异,就不再次解释了。

c + clang

 可以看到 a = ++i + ++i 语句的汇编和gcc有了明显区别。

// int i = 1;
mov [rbp-4], 1    // i 的地址 rbp-4
// int a = 0;
mov [rbp-8], 0    // a 的地址 rbp-8

// a = ++i + ++i;
mov eax, [rbp-4]    // 将 i 的值保存到 eax
add eax, 1          // 将 eax 加 1
mov [rbp-4], eax    // 将 eax 写入 rbp-4, 将2写入i
mov ecx, [rbp-4]    // 将 i 的值保存到 ecx
add ecx, 1          // 将 ecx 加 1
mov [rbp-4], ecx    // 将 ecx 写入 rbp-4, 将3写入i
add eax, ecx        // !!!重点,这里是 2+3
mov [rbp-8], eax    // 将 5 保存到 a

全篇总结

所以,有时候这种看起来很怪的代码,它的运行结果完全取决于编译器。

用到的概念:

  1. 函数栈,栈底指针(基址),栈顶指针(这个我没介绍),通过基址定位函数内变量
  2. 汇编 mov、add,汇编内存访问、操作

扩展阅读

网上找的一些,适合初学者看的入门小文章。

汇编语言运行时堆栈(内存数组) (biancheng.net)http://c.biancheng.net/view/3533.html汇编语言中mov、add、sub的可用操作对象 - 简书 (jianshu.com)https://www.jianshu.com/p/e69025b22d38汇编语言指令mov、add、sub、jmp详解 - html中文网https://www.html.cn/softprog/asse/120768.html浅析函数调用的栈帧 - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/425119402函数栈帧(详细图解)_ZHOUZH的博客-CSDN博客_函数调用栈帧过程(带图详解)https://blog.csdn.net/qq_41412237/article/details/119489211
C函数调用过程原理及函数栈帧分析 - 简书 (jianshu.com)https://www.jianshu.com/p/e7a22923867f

在线汇编网站

Compiler Explorer (godbolt.org)https://godbolt.org/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值