目录
1.反汇编代码
2.分析
1.栈区初始化
2.设置数组元素的值
3. 逐条分析范围for
1.arr的地址被放到[ebp-2Ch]处
2.[ebp-2Ch]指向的值被复制一份到[ebp-30h]处
3.eax暂存[ebp-2Ch]指向的值,加28h后存储到[ebp-34h]处
4.跳转指令
5.比较[ebp-30h]和[ebp-34h]指向的值,如果相等则跳转到return 0;的反汇编指令
6.将数组元素的值载入ecx寄存器,再将其赋值给变量a
7. 跳转指令
8.更新[ebp-30h]指向的值,每次执行都+4(原因:int类型的数组)
4.函数返回,空间释放
5.总结
几个重要参数
编译器为什么要这样设计汇编指令
3.范围for的核心思想
承接C7.【C++ Cont】范围for的使用和auto关键字文章的遗留任务
1.反汇编代码
#include <iostream>
using namespace std;
int main()
{
push ebp
mov ebp,esp
sub esp,78h
push ebx
push esi
push edi
mov ecx,offset _2A500742_leetcode@cpp
call @__CheckForDebuggerJustMyCode@4
nop
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
mov dword ptr [arr],1
mov dword ptr [ebp-24h],2
mov dword ptr [ebp-20h],3
mov dword ptr [ebp-1Ch],4
mov dword ptr [ebp-18h],5
mov dword ptr [ebp-14h],6
mov dword ptr [ebp-10h],7
mov dword ptr [ebp-0Ch],8
mov dword ptr [ebp-8],9
mov dword ptr [ebp-4],0Ah
for (int a : arr)
lea eax,[arr]
mov dword ptr [ebp-2Ch],eax
mov eax,dword ptr [ebp-2Ch]
mov dword ptr [ebp-30h],eax
mov eax,dword ptr [ebp-2Ch]
add eax,28h
mov dword ptr [ebp-34h],eax
jmp main+7Ah
mov eax,dword ptr [ebp-30h]
add eax,4
mov dword ptr [ebp-30h],eax
mov eax,dword ptr [ebp-30h]
cmp eax,dword ptr [ebp-34h]
je main+8Ch
mov eax,dword ptr [ebp-30h]
mov ecx,dword ptr [eax]
mov dword ptr [ebp-38h],ecx
{
}
jmp main+71h
return 0;
xor eax,eax
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
已知
jmp main+7Ah 跳转到add eax,4下方第一个mov eax,dword ptr [ebp-30h]
je main+8Ch 若符合条件,将跳转到xor eax,eax
jmp main+71h 跳转到add eax,4上方的mov eax,dword ptr [ebp-30h]
画图则为:
分析提示
汇编指令不能一条条看,需要有整体思想,按"指令块"的视角来看
2.分析
1.栈区初始化
push ebp
mov ebp,esp
sub esp,78h
push ebx
push esi
push edi
mov ecx,offset _2A500742_leetcode@cpp
call @__CheckForDebuggerJustMyCode@4
nop
为常规操作,具体分析见36.【C语言】函数栈帧的创建和销毁文章,不再赘述
2.设置数组元素的值
mov dword ptr [arr],1
mov dword ptr [ebp-24h],2
mov dword ptr [ebp-20h],3
mov dword ptr [ebp-1Ch],4
mov dword ptr [ebp-18h],5
mov dword ptr [ebp-14h],6
mov dword ptr [ebp-10h],7
mov dword ptr [ebp-0Ch],8
mov dword ptr [ebp-8],9
mov dword ptr [ebp-4],0Ah
从0x01到0x0A,依次被写入内存
注意这里的ebp寄存器,其值存储数组最后一个元素下一个位置的地址,后面会有重要作用,有几个重要的参数都依赖于ebp寄存器
3. 逐条分析范围for
1.arr的地址被放到[ebp-2Ch]处
lea eax,[arr]
mov dword ptr [ebp-2Ch],eax
2.[ebp-2Ch]指向的值被复制一份到[ebp-30h]处
mov eax,dword ptr [ebp-2Ch]
mov dword ptr [ebp-30h],eax
3.eax暂存[ebp-2Ch]指向的值,加28h后存储到[ebp-34h]处
mov eax,dword ptr [ebp-2Ch]
add eax,28h
mov dword ptr [ebp-34h],eax
eax原本存储arr数组的地址ebp-28h,现+28h
4.跳转指令
jmp main+7Ah
5.比较[ebp-30h]和[ebp-34h]指向的值,如果相等则跳转到return 0;的反汇编指令
mov eax,dword ptr [ebp-30h]
cmp eax,dword ptr [ebp-34h]
je main+8Ch
显然不满足,按顺序执行
备注: je main+8Ch是唯一能退出条件循环的出口
6.将数组元素的值载入ecx寄存器,再将其赋值给变量a
mov eax,dword ptr [ebp-30h]
mov ecx,dword ptr [eax]
mov dword ptr [ebp-38h],ecx
备注:变量a存储在[ebp-38h]处
不规范的写出前面两行汇编指令的等价指令mov ecx,dword ptr [[ebp-30h]]
写成C代码的形式
eax = *(ebp-30h);
ecx = *eax;
两次解引用,相当于
ecx = **(ebp-30h);
7. 跳转指令
jmp main+71h
8.更新[ebp-30h]指向的值,每次执行都+4(原因:int类型的数组)
mov eax,dword ptr [ebp-30h]
add eax,4
mov dword ptr [ebp-30h],eax
......多次条件循环直到[ebp-30h]和[ebp-34h]的值相等(数组元素全部遍历完了)才结束,跳转至return 0;
4.函数返回,空间释放
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
5.总结
几个重要参数
ebp寄存器:存储数组最后一个元素下一个位置的地址
[ebp-2Ch]指向的内存空间:存储arr数组首元素的地址
[ebp-30h]指向的内存空间:第一次存储arr数组首元素的地址,其后每次更新都+4
[ebp-34h]指向的内存空间:存储数组最后一个元素下一个位置的地址
[ebp-38h]指向的内存空间:存储变量a的值
★[ebp-30h]是决定能否退出条件循环的关键参数!
编译器为什么要这样设计汇编指令
遍历数组前需要准备开始位置(即初始索引)和结束位置(最后一个元素下一个位置的地址)
这两个数据需要内存空间去存储,因此用[ebp-2Ch]和[ebp-34h]暂时存储
还需要维护一个指针去逐个访问数组中的每个元素:因此用[ebp-30h]暂时存储
for (int a : arr)需要为a开辟内存空间,因此用[ebp-38h]暂时存储
3.范围for的核心思想
从头到尾遍历数组