关于C语言的函数调用机制
今天在sina Weibo上看到一个人发的一道面试题目,有关于被调用函数中局部变量的若干问题。于是我回忆函数的调用机制,但是感觉有点模糊,以前在做MIT的JOS的时候碰到过,但是记忆有些模糊了,于是利用一个简单的程序进行了过程的重温。
OS是gentoo,GCC-4.5.3--gcc -S *.c 进行反汇编
源代码如下
#include <stdio.h> struct tree{ int a; int b; };//之所以定义结构体是想更深入的了解下 struct tree foo(int a, char b) { struct tree m = {1,2}; m.a = a; m.b = b; return m; } int main(void) { int a = 2; char b = 3; struct tree m = {4,4}; m = foo(a, b); a = m.a; }
.file "1.c" .text .globl foo .type foo, @function foo: pushl %ebp movl %esp, %ebp subl $20, %esp movl 8(%ebp), %ecx movl 16(%ebp), %eax movb %al, -20(%ebp) movl $1, -8(%ebp) movl $2, -4(%ebp) movl 12(%ebp), %eax movl %eax, -8(%ebp) movsbl -20(%ebp), %eax movl %eax, -4(%ebp) movl -8(%ebp), %eax movl -4(%ebp), %edx movl %eax, (%ecx) movl %edx, 4(%ecx) movl %ecx, %eax leave ret $4 .size foo, .-foo .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $28, %esp movl $2, -4(%ebp) movb $3, -5(%ebp) movl $4, -16(%ebp) movl $4, -12(%ebp) movsbl -5(%ebp), %edx leal -16(%ebp), %eax movl %edx, 8(%esp) movl -4(%ebp), %edx movl %edx, 4(%esp) movl %eax, (%esp) call foo subl $4, %esp movl -16(%ebp), %eax movl %eax, -4(%ebp) leave ret .size main, .-main .ident "GCC: (Gentoo 4.5.3-r2 p1.5, pie-0.4.7) 4.5.3" .section .note.GNU-stack,"",@progbits
其中指令比较有难度的就是
call 函数调用指令,在main函数中我们调用了foo函数,对应的汇编指令就是
call foo,操作系统做的工作就是sp=sp-4,然后将ip入栈,更换ip指向foo的指令
leave,将ebp的值赋给esp,然后pop出来ebp。注意pop 指令先读取出esp 对应栈单元的值,然后esp=esp+4;push指令先esp=esp-4,在将值存入esp所指的栈单元。
ret,函数返回指令,根据当前的esp读取出eip的值,然后返回主函数的指令
下面根据汇编代码和内存栈的变化说明程序的执行过程以及函数调用如何得到返回值
执行到调用call foo时内存栈的情况如下
注意leal -16(%ebp), %eax ;将ebp-16的值赋给eax,通过记录结构体内存单元的地址实现返回值的使用。
call指令执行过后,执行到处理返回值之前,内存单元的情况如:
下面是函数是如何将返回值传递给主函数的
通过ebp,esp的变换,在调用函数中实现了返回值的传递。
下面执行到leave指令,复原esp,ebp,然后ret返回到main函数中。
上诉实验说明了很多东西以前貌似懂了,但是讲的话难度还是很大。反正赋闲在家,有心情的话还想把JOS重新做一边,顺便把很多书上的东西巩固一下。