一个函数的调用包括将数据(以参数和返回值形式)和控制代码的一部分传递到另一部分。在函数调用是,必须为被调用的函数的局部变量分配空间,并在退出时释放这些空间。参数传递和被局变量的分配和释放空间则是通过操纵程序栈来实现。
那么我们则得了解一个函数栈的结构:
机器用栈来传递函数参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。下图则是一个简单的栈帧结构:
上图我们看到了两个重要的指针,分别是%esp(栈指针),%ebp()帧指针,也叫基址指针,其中%esp永远指向栈顶,而%ebp 主要用于访问变量,下面我们通过一个简单的程序来了解下函数的调用(ubunt64 位编译):
源代码:
int be_called(int*,int*);
int caller()
{
int a =1;
int b = 2;
be_called(&a,&b);
return 0;
}
int be_called( int *a, int *b )
{
int rtrn_val;
rtrn_val = *a + *b;
return rtrn_val;
}
汇编代码:
64位系统 栈指针 rsp, 帧指针rbp
caller:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $1, -8(%rbp) // (rbp-8)
movl $2, -4(%rbp) //
leaq -4(%rbp), %rdx
leaq -8(%rbp), %rax
movq %rdx, %rsi // &b ps:由于是64位CPU系统,使用了寄存器传参 ,- -!
movq %rax, %rdi // &a
call be_called
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
be_called:
.LFB1:
.cfi_startproc
pushq %rbp // store rbp
.cfi_def_cfa_offset 16 // 关于这个值为16,可以看这里解释http://stackoverflow.com/questions/7534420/gas-explanation-of-cfi-def-cfa-offset
.cfi_offset 6, -16
movq %rsp, %rbp //set rbp = rsp
.cfi_def_cfa_register 6
movq %rdi, -24(%rbp) //&a
movq %rsi, -32(%rbp) //&b
movl $0, -4(%rbp) // 临时变量rtrn_val = 0; &rtrn_val = rbp -4
movq -24(%rbp), %rax
movl (%rax), %edx
movq -32(%rbp), %rax
movl (%rax), %eax
addl %edx, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax // rtrn_val = *a + *b
popq %rbp // restore rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
嗯,大概就是这样,貌似64位系统没有很好的表现出了上面那张图,不过其实理解起来擦不多 。