调用函数时栈中信息的变化(C语言)
用栈来支持过程的 嵌套调用,过程的入口参数、返回地址、被保存寄存器的值、被调用过程中的非静态局部变量等都会被压入栈中。
在代码执行过程中,每一个过程都会有自己的栈区,称为栈帧,一个栈由若干栈帧组成,每个栈帧用专门的 栈帧寄存器(EBP)指定起始位置。因此,当前栈帧的范围在栈指针 EBP和栈指针 ESP指向区域之间。
栈
栈的地址是从高地址向低地址增长,栈底指向低地址,栈顶指向高地址,栈中每弹出一个值,栈顶指针向高地址移动,如果存储一个int型数据,则栈顶地址加4,每压栈一次,栈顶指针-4。
此处有误,中间是相差16个字节
例如如下一段代码
#include<stdio.h>
int add(int x, int y)
{
return x + y;
}
int caller()
{
int x = 5,y = 10;
int sum = add(x, y);
return sum;
}
int main()
{
caller();
}
下面是它的汇编代码:
caller:
0x401564 push %rbp //保存main函数栈底地址,压栈
0x401565 mov %rsp,%rbp //将main函数的栈顶地址放到%rbp中,栈底移动到%rsp的位置,以便建立新栈帧
0x401568 sub $0x30,%rsp //%rsp中的数据减30,开辟caller函数的栈帧
0x40156c movl $0x5,-0x4(%rbp)//将数据0x5放入相对于栈底-4的地址
0x401573 movl $0xa,-0x8(%rbp)//将数据0xa放入相对于栈底-8的地址
0x40157a mov -0x8(%rbp),%edx//将相对于栈底-8的地址中的数据放入寄存器%edx
0x40157d mov -0x4(%rbp),%eax//将相对于栈底-4的地址中的数据放入寄存器%eax
0x401580 mov %eax,%ecx //将%eax中的数据移动到%ecx
0x401582 call 0x401550 <add>//调用add 函数
0x401587 mov %eax,-0xc(%rbp) //取出返回值放到相对于栈底-0xc的地址中(返回值规定放在eax寄存器中)
0x40158a mov -0xc(%rbp),%eax //将相对于栈底-0xc的地址中的数据放到%eax作为返回值
0x40158d add $0x30,%rsp //栈顶地址加0x30,销毁栈
0x401591 pop %rbp //弹出栈顶的值,放入栈底寄存器中
0x401592 ret //返回
add:
0x401550 push %rbp //将caller的栈底地址压栈
0x401551 mov %rsp,%rbp // 将caller栈顶的地址放入栈底寄存器中,以建立新的栈帧
0x401554 mov %ecx,0x10(%rbp)//将第一个参数放入相对于add 的栈底为0x10地址中
0x401557 mov %edx,0x18(%rbp)//将第二个参数放入相对于add 的栈底为0x18地址中
0x40155a mov 0x10(%rbp),%edx //将第一个参数放到%edx中
0x40155d mov 0x18(%rbp),%eax //将第二个参数放到%eax中
0x401560 add %edx,%eax //将寄存器%dex的数据加上%eax中的数据放到%eax中
0x401562 pop %rbp //弹出栈顶的值,放入栈底寄存器中,销毁栈
0x401563 ret //返回
当add函数运行完之后,栈顶指针ESP会被赋予caller函数的栈顶地址,栈底也会变成caller函数的栈底地址,然后销毁此add函数的栈。
函数的返回值会被规定存在寄存器eax中。
栈底的地址+8存的是调用此函数的命令的下一条命令的地址,执行完add函数之后,会弹出栈顶,以便继续执行下一条命令。