1.内存布局
⦁ 栈:维护函数调用时的上下文
⦁ 堆:应用程序动态分配的内存区域
⦁ 可执行文件映像:存储可执行文件在内存里的映像
⦁ 动态链接库映射区:存储动态链接库在内存里的映像
⦁ 保留区:禁止访问的内存区域
图:Linux进程地址空间布局(内核版本2.4.x)
2.栈
栈保存了一个函数调用所需要维护的信息:
⦁ 函数的返回地址和参数
⦁ 临时变量:函数的非静态局部变量及编译器自动生成的其他临时变量
⦁ 上下文:函数调用前后需要保持不变的寄存器
在i386中,函数的活动记录用ebp和esp这两个寄存器划定范围,esp寄存器始终指向栈的顶部,ebp寄存器指向了函数活动的一个固定位置。ebp可以用来定位函数活动记录中各个数据。
图: 活动记录
函数的开头:
push ebp | 把ebp压入栈中(称为old ebp) |
move ebp , esp | ebp = esp (此时ebp指向栈顶) |
[可选] sub esp , ××× | 在栈上分配×××字节的临时空间 |
[可选] push ××× | 保存名为×××的寄存器(可重复多个) |
函数的结尾:
[可选] pop ××× | 恢复的寄存器(可重复多个) |
move esp , ebp | 恢复esp同时回收局部变量空间 |
pop ebp | 从栈中恢复保存ebp的值 |
ret | 从栈中取得返回地址,并跳转到该位置 |
2.1调用惯例
函数的调用方和被调用方对函数如何调用,应该有明确的约定,这样的约定称为调用惯例。
⦁ 函数参数的传递顺序和方式
⦁ 栈的维护方式
⦁ 名字修饰的策略
C语言中存在多个调用惯例,默认的是cdecl,cdecl的内容如表:
参数传递 | 从右至左的顺序将参数入栈 |
出栈方 | 函数调用方 |
名字修饰 | 在函数名称前加1个下划线 |
2.2 实例
void f(int x, int y)
{
...
return ;
}
int main()
{
f(1,3);
return 0;
}
图: main函数的执行流程