-
栈在函数调用中的作用:
- 机器使用栈来传递过程参数、存储返回地址、保存寄存器以备以后恢复,以及本地存储。为单个过程分配的那部分栈称为栈帧。
- 栈帧是程序栈的一部分,有两个端点:一个标识起始地址(通常由帧指针 EBP 指向),一个标识结束地址(通常由栈指针 ESP 指向)。
-
栈帧的结构:
- 由一系列栈帧组成,每个栈帧对应一个函数。每个栈帧都建立在调用者的下方,当被调用者执行完毕时,该栈帧会被释放。
- 栈帧向地址递减方向延伸,减小栈指针 ESP 相当于分配内存,增加栈指针 ESP 相当于释放内存。
-
过程实现步骤:
- 备份原来的帧指针:
- 将当前帧指针 EBP 备份到栈中。
- 调整栈帧指针:
- 将当前的栈指针 ESP 的值赋给帧指针 EBP,以建立新的栈帧。
- 为被调用者准备栈帧:
- 分配临时变量所需的预留内存。
- 使用栈帧:
- 使用
mov
、push
和pop
等指令读取和写入数据。
- 使用
- 恢复被调用者寄存器的值:
- 将栈帧中备份的值恢复到寄存器中。
- 释放被调用者的栈帧:
- 将栈指针 ESP 指向帧指针 EBP,释放栈帧。
- 恢复调用者的栈帧:
- 调整栈帧的端点,使当前栈帧区域回到原始位置。
- 弹出返回地址:
- 弹出返回地址,并跳出当前过程,继续执行调用者代码。
- 备份原来的帧指针:
-
过程调用和返回指令:
- call 指令:跳转到被调用函数并将返回地址压入栈中。
- leave 指令:清理当前栈帧,恢复调用者的帧指针和栈指针。
- ret 指令:从栈中弹出返回地址,并跳转到该地址继续执行调用者代码。
代码示例和注释
下面是一个简单的 C++ 函数和对应的汇编代码示例:
C++ 代码示例
#include <iostream>
void foo(int a, int b) {
int result = a + b;
std::cout << "Result: " << result << std::endl;
}
int main() {
foo(5, 3);
return 0;
}
对应的汇编代码示例
假设我们使用 x86 汇编:
main:
push ebp ; 备份调用者的帧指针
mov ebp, esp ; 设置新的帧指针
sub esp, 16 ; 分配栈空间
push 3 ; 参数 b
push 5 ; 参数 a
call foo ; 调用 foo 函数
add esp, 8 ; 清理参数
mov esp, ebp ; 恢复栈指针
pop ebp ; 恢复帧指针
ret ; 返回调用者
foo:
push ebp ; 备份调用者的帧指针
mov ebp, esp ; 设置新的帧指针
sub esp, 16 ; 分配栈空间
mov eax, [ebp+8] ; 获取参数 a
mov ecx, [ebp+12] ; 获取参数 b
add eax, ecx ; 计算 a + b
mov [ebp-4], eax ; 将结果存储到局部变量 result
; std::cout << "Result: " << result << std::endl;
; ... 省略输出代码 ...
mov esp, ebp ; 恢复栈指针
pop ebp ; 恢复帧指针
ret ; 返回调用者
详细注释
-
main 函数:
push ebp
:将调用者的帧指针压入栈中。mov ebp, esp
:设置新的帧指针。sub esp, 16
:为局部变量分配栈空间。push 3
和push 5
:将参数3
和5
压入栈中。call foo
:调用foo
函数,将返回地址压入栈中,并跳转到foo
。add esp, 8
:清理参数,将栈指针恢复到调用foo
前的状态。mov esp, ebp
:恢复栈指针。pop ebp
:恢复帧指针。ret
:从栈中弹出返回地址,并跳转回调用者。
-
foo 函数:
push ebp
:将调用者的帧指针压入栈中。mov ebp, esp
:设置新的帧指针。sub esp, 16
:为局部变量分配栈空间。mov eax, [ebp+8]
:获取参数a
。mov ecx, [ebp+12]
:获取参数b
。add eax, ecx
:计算a + b
。mov [ebp-4], eax
:将结果存储到局部变量result
。mov esp, ebp
:恢复栈指针。pop ebp
:恢复帧指针。ret
:从栈中弹出返回地址,并跳转回调用者。
这些汇编指令展示了函数调用过程中栈帧的管理和数据的传递过程。