目录
有说 ++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
全篇总结
所以,有时候这种看起来很怪的代码,它的运行结果完全取决于编译器。
用到的概念:
- 函数栈,栈底指针(基址),栈顶指针(这个我没介绍),通过基址定位函数内变量
- 汇编 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
在线汇编网站