【编译器】栈帧(Stack Frame)

栈帧Stack Frame是函数调用时在栈上分配的内存片段,用于局部变量、参数传递和返回地址的保存。每个函数调用都有独立的栈帧,由ebp和esp寄存器管理。在函数P调用函数Q的过程中,会进行ebp和esp的调整,参数从右向左压栈,返回地址也被保存。通过汇编代码可以更深入理解这一过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈帧(Stack Frame)是栈上的内存片段,用来实现函数调用。其主要作用有:为局部变量和临时变量分配空间,传递参数,保存返回地址

每个函数在栈上拥有独立的帧,这些帧是在函数调用时分配的。在任一时刻,寄存器ebp(帧指针)都指向帧的起始处,寄存器esp(栈指针)指向帧的结尾处,它们之间的部分是当前函数的栈帧。我们可以这样认为,ebp是局部的,作用于当前帧,而esp是全局的,作用于整个栈。

 ------------   栈底
|     .      |
|     .      |
|     .      |
 ------------
| 上一帧的ebp |  由被调用函数保存上一帧的ebp
|被保存的寄存器|
|  局部变量   |
|  临时变量   |
|     .      |  未分配区域
|     .      |
|     .      |

|   参数n    |  注意参数的压栈顺序(从右向左)
|  参数n-1   |
|     .      |
|     .      |
|     .      |

|   参数1    |
|  返回地址   |  由调用者保存返回地址
 ------------
| 上一帧的ebp |  <-ebp的值
|     .      |
|     .      |
|     .      |  <-esp的值
 ------------   栈顶

假设函数P(caller)调用函数Q(callee),一般来说在P中会执行如下操作:

  1. push %ebp; move %esp, %ebp; 保存上一帧的ebp,以便恢复,并将ebp指向帧头
  2. sub $NUM, %esp; 为局部变量和临时变量分配空间,其中NUM为字节数,在编译时已经确定
  3. 将参数从右到左依次压栈
  4. 将返回地址压栈
  5. 跳转到函数Q的第一条语句
  6. 将返回值(如果有)保存在eax中
  7. 释放空间,恢复信息(如ebp)
  8. 弹出返回地址并返回

举个例子:

int caller(int m, int n)
{
	return m + n + callee(m, n);
}

int callee(int x, int y)
{
	return x + y;
}

由gcc编译之后的汇编代码如下:

caller:
	pushl	%ebp		//将上一帧的帧指针压栈
	movl	%esp, %ebp	//令ebp指向帧头
	pushl	%ebx
	subl	$20, %esp	//为何要分配20个字节
	movl	12(%ebp), %eax	//获取参数n
	movl	8(%ebp), %edx	//获取参数m
	leal	(%edx,%eax), %ebx	//leal指令,相当于ebx = edx + eax = m + n
	movl	12(%ebp), %eax
	movl	%eax, 4(%esp)	//参数n压栈,注意n先于m压栈
	movl	8(%ebp), %eax
	movl	%eax, (%esp)	//参数m压栈
	call	callee		//返回地址压栈,并跳转到callee
	leal	(%ebx,%eax), %eax	//此时ebx的值为m+n,eax(前者)的值为callee的返回值
	addl	$20, %esp	//释放空间
	popl	%ebx	//恢复信息
	popl	%ebp
	ret		//弹出返回地址并返回

callee:
	pushl	%ebp
	movl	%esp, %ebp
	movl	12(%ebp), %eax
	movl	8(%ebp), %edx
	leal	(%edx,%eax), %eax
	popl	%ebp
	ret
注意到一个细节,为了保存参数m和n,caller函数在栈上分配了20个字节,但实际只需要8个字节,这是什么原因呢?
 -----
| ebp |
| ebx |
|  .  |
|  .  |
|  .  |
|  n  |
|  m  |
| ra  |
 -----

这是因为在IA32架构中,栈帧的大小总是16的倍数。通过研究栈帧的内容,可以发现寄存器ebp、ebx再加上返回地址ra总共是12个字节,因此栈帧的总大小为32。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值