6. C 函数调用机制概述

​ 在 Linux 内核程序 boot/head.s 执行完基本初始化操作之后,就会跳转区执行 init/main.c 程序。那么 head.s 程序时如何把执行控制转交给 init/mian.c 程序呢?即汇编程序时如何调用执行 C 语言程序的?这里我们首先描述一下 C 函数的调用机制、控制器传递方式,然后说明 head.s 程序跳转到 C 程序的方法。

​ 函数调用操作包括从一块代码到另一块代码之间的双向数据传递和执行控制转移。数据传递通过函数参数和返回值来进行。另外,我们还需要在进入函数时未函数的局部变量分配存储空间,并且在退出时收回这部分空间。Intel 80x86 CPU 为控制传递提供了简单的指令,而数据的传递和局部变量存储空间的分配与回收则通过栈操作来实现。

7. 栈帧结构和控制转移权方式

​ 大多数 CPU 上的程序实现使用栈来支持函数调用操作。栈被用来传递参数、存储返回信息、临时保存寄存器原有值以备恢复以及用来存储局部数据。单个函数调用操作所使用的栈部分被被称为栈帧(Stack frame)结构,其通常结构如下。栈帧结构的两端由两个指针来指定。寄存器 ebp 通常用作帧指针(frame pointer),而 esp 则用作栈指针(stack pointer)。在函数执行过程中,栈指针 esp 会随着数据的入栈和出栈而移动,因此函数中对大部分数据的访问都基于帧指针 ebp 进行。

​ 对于函数 A 调用函数 B 的情况,传递给 B 的参数包含在 A 的栈帧中。当 A 调用 B 时,函数 A 的返回地址(调用返回后继续执行的指令地址)被压入栈中,栈中该位置也明确指明了 A 栈帧的结束处。而 B 的栈帧则从随后的栈部分开始,即图中保存栈指针(ebp)的地方开始。再随后则用于存放任何保存的寄存器值以及函数的临时值。

​ B 函数通亚航也使用栈来保存不能放在寄存器中的局部变量值。例如由于通常 CPU 的寄存器数量有限而不能够存放函数的所有局部数据,后者有些局部变量是数组或 结构,因此必须使用数组或结构引用来访问。还有就是 C 语言的地址操作符 ‘&’ 被应用到一个局部变量上时,我们就需要为该变量生成一个地址,即为变量的地址指针分配以空间。最后,B 函数会使用栈来保存调用任何其它函数的参数。

​ 栈是往低(小)地址方向扩展的,而 esp 指向当前栈顶处的元素。通过使用 push 和 pop 指令我们可以把数据压入栈中或从栈中弹出。对于没有指定初始值的数据所需要的存储空间,我们可以通过把栈指针递减适当的值来做到。类似地,通过增加栈指针值我们可以回收栈中已分配的空间。

​ 指令 CALL 和 RET 用于处理函数调用和返回操作。调用指令 CALL 的作用是把返回地址压入栈中并且跳转到被调用函数开始处执行。返回地址是程序中紧随调用指令 CALL 后面一条指令的地址。因此当被调函数返回时就会从该位置继续执行。返回指令 RET 用于弹出栈顶处的地址并跳转到该地址处。在使用该指令之前,应该先正确处理栈中内容,使得当前栈指针所指位置内容正是先前 CALL 指令保存得返回地址。另外,若返回值睡个整数或一个指针,那么寄存器 eax 将被默认用来传递返回值。

​ 尽管某一时刻只有一个函数在执行,但我们还是需要确定一个函数(调用者)调用其他函数(被调用者)时,被调用者不会修改会覆盖调用者今后要用到的寄存器内容。因此 Intel CPU 采用了所有函数必须遵守的寄存器用法统一惯例。该惯例指明,寄存器 eax、edx 和 ecx 的内容必须有调用者自己负责保存。当函数 B 被 A 调用时,函数 B 可以在不用保存这些寄存器内容的情况下任意使用它们而不会毁坏函数 A 所需要的任何数据。另外,寄存器 ebx、esi 和 edi 的内容则必须有被调用者 B 来保护。当被调用者需要使用这些寄存器中的任意一个时,必须首先在栈中保存其内容,并在退出时恢复这些就餐器的内容。因为调用者 A (或者一些更高层的函数)并不负责保存这些寄存器内容,但可能在以后的操作中还需要用到原先的值。还有寄存器 ebp 和 esp 也必须遵守第二个惯例用法。

8. 函数调用举例

​ 作为一个例子,我们来观察下面 C 程序 exch.c 中函数调用的处理过程。

[root@rockman 0710]# cat exch.c
#include <stdio.h>
void swap(int* a, int* b)
{
int t = 0;
t = *a;
*a = *b;
*b= t;
} int main()
{
int a = 16, b = 32;
printf("a=%d,b=%d\n", a, b);
swap(&a, &b);
printf("a=%d,b=%d\n", a, b);
return 0;
}

其中函数 swap() 用于交换两个变量的值。C 程序中的主程序 main() 也是一个函数,它在调用了 swap() 之后返回交换后的结果。这两个函数的栈帧结构如下图所示。

可以看出,函数 swap() 从调用者(mian())的栈帧中获取其参数。图中的位置信息相对于寄存器 ebp 中的帧指针。栈帧左边的数字指出了相对于帧指针的地址偏移值。在像 gdb 这样的调试器中,这些数值都用 2 的补码表示。例如 ‘-4’ 被表示成 ‘0xFFFFFFFC’,‘-12’ 会被表示成 ‘0xFFFFFFF4’。

​ 调用者 main() 的栈帧结构中包括局部变量 a 和 b 的存储空间,相对于帧指针位于 -4 和 -8 偏移处。由于我们需要为这两个局部变量生成地址。因此他们必须保存在栈中而非常简单地存放在寄存器中。

10GNU C语言函数调用的更多相关文章

  1. 测试c语言函数调用性能因素之测试三

    函数调用:即调用函数调用被调用函数,调用函数压栈,被调用函数执行,调用函数出栈,调用函数继续执行的一个看似简单的过程,系统底层却做了大量操作. 操作: 1,               调用函数帧指针 ...

  2. C语言函数调用过程,汇编角度查看

    C语言函数调用过程,汇编角度查看 把函数的参数按照调用约定压栈或者存储到寄存器中 调用要使用的函数,先把调用者的地址入栈,方便回来 跳转到函数 把函数使用到的一些寄存器压栈,避免修改寄存器的值 执行函 ...

  3. C语言函数调用时候内存中栈的动态变化详细分析(彩图)

    版权声明:本文为博主原创文章,未经博主允许不得转载.欢迎联系我qq2488890051 https://blog.csdn.net/kangkanglhb88008/article/details/8 ...

  4. C语言函数调用完整过程

    C语言函数调用详细过程 函数调用是步骤如下: 按照调用约定传参 调用约定是调用方(Caller)和被调方(Callee)之间按相关标准 对函数的某些行为做出是商议,其中包括下面内容: 传参顺序:是从左 ...

  5. C语言函数调用栈

    C语言函数调用栈 栈溢出(stack overflow)是最常见的二进制漏洞,在介绍栈溢出之前,我们首先需要了解函数调用栈. 函数调用栈是一块连续的用来保存函数运行状态的内存区域,调用函数(calle ...

  6. C语言函数调用约定

    在C语言中,假设我们有这样的一个函数: int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数.但是,当高级语言被编译 ...

  7. C语言函数调用栈(二)

    5 函数调用约定 创建一个栈帧的最重要步骤是主调函数如何向栈中传递函数参数.主调函数必须精确存储这些参数,以便被调函数能够访问到它们.函数通过选择特定的调用约定,来表明其希望以特定方式接收参数.此外, ...

  8. 关于C语言函数调用压栈和返回值问题的疑惑

    按照C编译器的约定调用函数时压栈的顺序是从右向左,并且返回值是保存在eax寄存器当中.这个命题本该是成立的,下面用一个小程序来反汇编观察执行过程: #include<stdio.h> in ...

  9. C语言函数调用栈(一)

    程序的执行过程可看作连续的函数调用.当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行.函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call sta ...

随机推荐

  1. [题解]luogu_P2155_BZOJ_2186沙拉公主的困惑

    题意求1~N!中与M!互质的数的个数, 首先证明gcd(a,b)=1时gcd(a-kb,b)=1 gcd(a,b)=1 gcd(a%b,b)=1 gcd(a-kb,b)=1 即a-kb与b互质 这样由 ...

  2. 073 Set Matrix Zeroes 矩阵置零

    给定一个 m x n 的矩阵,如果一个元素为 0 ,则将这个元素所在的行和列都置零.你有没有使用额外的空间?使用 O(mn) 的空间不是一个好的解决方案.使用 O(m + n) 的空间有所改善,但仍不 ...

  3. 在 WPF 中的线程

    线程处理使程序能够执行并发处理,以便它可以做多个操作一次.节省开发人员从线程处理困难的方式,设计了 WPF (窗口演示文稿基金会).这篇文章可以帮助理解线程在 WPF 中的正确用法. WPF 内部线程 ...

  4. 【Unity3D】3D游戏学习

    1.理解游戏元素,简单回答以下问题: 1.1简单介绍一款上世纪(19XX)出品的计算机游戏,然后按课件的方法描述游戏的五个基本元素.(讲目标.玩法.规则) 1.1.1仙剑奇侠传 <仙剑奇侠传&g ...

  5. android studio项目提交Git@OSC

    转载地址:http://www.bubuko.com/infodetail-977061.html 先到git.oscchina.net网站上申请个账号,然后创建一个项目.过程不再说了. 新建工程后, ...

  6. UI设计中蕴涵着系统重要的数据结构与功能设计

    UI设计中蕴涵着系统重要的数据结构与功能设计 UI设计中的用户需求,事件(用例)驱动

  7. 如何用Windows PowerShell替换命令提示符

    在Windows 10的"开始"按钮中将PowerShell替换为命令提示符,这不是很好吗?我知道你会有疑问,为什么要这样做?可能会失去了运行DOS命令的能力.好吧,让我解释一下. ...

  8. 第一篇Active Directory疑难解答概述(2)

    从故障诊断的角度来看,无论用户对象存在于哪个Active Directory域中,Exchange都需要访问此数据.这意味着所有包含启用Exchange的对象的域必须对其运行Setup / Prepa ...

  9. [Eclipse] - 解决"Java was started but returned exit code = 13"问题

    最近遇到一个问题,打开Eclipse时会弹出如下对话框 在网上查找了相关资料后结合自己的问题,总结如下. 导致问题的原因: 1. 通常的原因是安装的Eclipse的版本或者Java JDK的版本不匹配 ...

  10. 洛谷 P1001 A+B Problem

    题目描述 输入两个整数a,b,输出它们的和(|a|,|b|<=10^9). 注意 1.pascal使用integer会爆掉哦! 2.有负数哦! 3.c/c++的main函数必须是int类型,而且 ...