寄存器
32位操作系统的寄存器:
大部分EXE都是64位了, 这里只记录64的情况:
MSVC使用RSP作为栈顶指针(注意栈的增长方向)。
函数调用约定
C/C++代码默认使用__cdecl约定。x86的程序中, 参数的转递是靠栈, x64中参数的传递是靠寄存器, 只有当参数个数大于6时才回使用栈。
函数调用过程
class FunctionCall {
public:
void foo(int a1, int a2, float f1, float f2, int a3, int a4, int a5) {
std::printf("a1:%d, %d, %0.2f, %0.2f, %d, %d, %d\n", a1, a2, f1, f2, a3, a4, a5);
}
private:
int a_ = 0;
};
int main() {
FunctionCall call;
call.foo(1, 2, 3.0, 4.0, 5, 6, 7);
return 0;
}
汇编代码解释:
00007FF7DEB96E14 lea rcx,[call]
00007FF7DEB96E19 call FunctionCall::FunctionCall (07FF7DEB91299h)
00007FF7DEB96E1E mov dword ptr [rsp+38h],7 # 参数入栈, 注意入栈顺序
00007FF7DEB96E26 mov dword ptr [rsp+30h],6
00007FF7DEB96E2E mov dword ptr [rsp+28h],5
00007FF7DEB96E36 movss xmm0,dword ptr [__real@40800000 (07FF7DEB9B144h)] # 浮点数放到xmm0-xmm3寄存器
00007FF7DEB96E3E movss dword ptr [rsp+20h],xmm0
00007FF7DEB96E44 movss xmm3,dword ptr [__real@40400000 (07FF7DEB9B140h)]
00007FF7DEB96E4C mov r8d,2 #因为只有2个整形, 所以只用了edx和r8寄存器
00007FF7DEB96E52 mov edx,1
00007FF7DEB96E57 lea rcx,[call] #取call变量的地址,也就是this指针的值到rcx寄存器
00007FF7DEB96E5C call FunctionCall::foo (07FF7DEB91294h) # call
函数栈帧
栈帧(Stack Frame)是每次函数调用时,在线程栈上为该函数分配的一块内存区域,用于存储该函数的局部变量、返回地址、参数、保存的寄存器等信息。
每调用一个函数,系统就会在栈上“推入”一个新的栈帧;函数返回时再“弹出”这个栈帧。
举个例子:C++ 函数调用过程
int add(int a, int b) {
int result = a + b;
return result;
}
int main() {
int sum = add(2, 3);
}
当 main() 调用 add(2, 3) 时,大致会发生:
- 当前 CPU 会记录返回地址(也就是 main() 中调用 add 的下一条指令)
- 系统会为 add() 在线程栈上开辟一个栈帧
- 函数参数 a = 2,b = 3
- 局部变量 result
- 保存调用者的一些寄存器(如 rbp、rsp、rdi)
- 当 add() 返回时:
- 弹出栈帧
- 恢复寄存器
- 程序回到 main() 中继续执行
栈帧通常包含哪些内容?
| 内容 | 描述 |
| —————————— | ———————————- |
| 返回地址(Return Address) | 函数调用返回后,跳转到调用点的地址 |
| 上一个栈帧指针(Saved rbp
) | 用于恢复上一个函数的栈帧 |
| 函数参数(参数传递方式视架构) | 有的在栈中,有的在寄存器中 |
| 局部变量 | 当前函数定义的变量 |
| 临时寄存器保存区域 | 某些调用约定需要保存的寄存器 |