code2012

加油,坚持,努力,自信
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

从汇编看c语言函数调用

Posted on 2011-05-17 23:16  code2012  阅读(502)  评论(0编辑  收藏  举报
从汇编看c语言函数调用
http://www.itbbs.cn/manual/linux-c/ch19.html
 <linux 一站式学习>

 

    学C语言时,就听老师说函数调用时是通过栈来记录信息,又听说什么“保留现场”,"恢复现场"一些既听不懂,也不知道怎么弄懂的东西。最近正在学习 Linux下的汇编,现在就通过一个简单的例子来展示一下汇编级的函数调用,这样能够增加大家对C语言的理解。虽然并不是很完善,但是足够阐明函数调用的思想。

 

//functest.c

 #include <stdio.h>

void func(int a, int b)

{

   int c;

   int d;

    c= a;

    d= b;

}

int main()

{

   func(2, 3);

   return 0;

}

    在Linux下通过命令gcc -S functest.c,可以生成汇编程序functest.s

 

    首先可以看到一点,一个简单的C程序的汇编却是如此之长,可以想到良好的程序书写习惯有多大的好处。

    言归正传,我们不分析每条指令都是什么意思,我们重点看函数调用那一部分。

    我们注意在调用call func之前,第25,26,27行是为func准备参数。首先把%esp减去8个字节,这是因为在Linuxint型为4个字节(想想谭浩C语言程序设计书中说int2个字节?这东西依赖于具体的操作系统和编译器),即手动修改栈指针。可以发现C语言中函数参数是保存在栈中的,进一步,我们发现gcc3先压入栈中,之后是2,可以说明,C中函数参数的压栈顺序和函数书写顺序正好相反。执行完这3条指令后,栈中的内容如下:





    之后28行调用func,通过call指令。执行call指令时,不仅仅去调用func函数,而且做了一个我们程序员看不到的动作,就是把下一条指令的地址压入到栈中。这点我们稍后会提到,而且从这我们也能略窥到,函数名只不过是地址的别名,只不过是为了方便程序员理解,我们可以完全用16进制地址取代这些别名,当然计算机并不觉得用地址比用别名更困难。

    现在我们进入func。6,7行的两条指令可以认为是套路,具体原因可以见[注1]。先把%ebp的值压入栈,之后把栈顶赋给%ebp,隐约我们可以猜 到,因为在这里修改了%ebp,而又不想原来的%ebp被覆盖,那么只好先把原来的%ebp存储到栈中,必要的时候可以在栈中恢复原来的%ebp。第8 行,将栈顶向下移,这个操作的目的是为函数中的临时变量在栈中分配存储空间。有人会想为什么不用其它的内存或者寄存器去存储临时变量?这是因为如果为大型 项目编写程序,要跟踪哪些变量使用内存,而哪些使用寄存器简直就是恶梦,所以C中用栈存储临时变量,因为在栈在函数调用后会释放或者清空,那么这些变量是 不可能被其它函数所调用,故这也是“局部变量”的由来。这个行为也使程序员在函数中要对操作得非常小心,例如我们在函数中开了一个很大的数组,那么在为这 个数组开辟栈空间时,很可能出现段错误。执行完6,7,8三行后,栈中的内容如下:



 

9,10行是把2赋值给d11,12是把3赋值给c。那么栈中的内容确定下来如下:

 
     可以看到,有两个为局部变量开辟的占空间实际并没有被使用到,看来gcc在生成汇编的时候,对需要多少栈空间计算的并不很精确。有人现在有疑问那个标记”XXXX”的单元是干什么用的,现在我来告诉你那个其实就是函数的返回地址,记得我前面说过,call指令会把调用函数后的下一条指令地址入栈,那么那个地址就存在XXXX那个单元中。那么完整的栈内容如下:


这样函数主要执行完毕,leave是恢复调用func之前的栈内容和%ebp内容,完全可以用以下指令替代:

movl %ebp, %esp

popl %ebp

 

ret 指令就是返回到主程序,不赘述。

30行为主程序中从栈中删除调用func时的参数2,3

这样一个函数的完整调用过程就给大家展示完了,希望对大家有所帮助。本人刚学汇编,如有错误,还望大家指出。

 

 

1:这样做,是为了对函数参数容易进行读取。在设计之初,考虑到用根据%esp和偏移量来对栈中的参数进行访问,但是由于在函数中%esp极有可能改变,那么维护这个偏移量也变的复杂,所以利用%ebp记录进入函数时栈指针的位置,而%ebp在这个函数中不会被改变,那么通过%ebp去定位函数参数就变的非常容易,所以一般有人把%ebp也称为栈底指针,而这也成为了汇编程序员的习惯。