函数堆栈

程序要想运行,首先要由操作系统负责为其创建进程,并在进程的虚拟地址空间中为其代码段和数据段建立映射。光有代码段和数据段是不够的,进程在运行过程中,还要有其动态环境,其中最重要的就是堆栈。下图是linux下进程的地址空间布局:

linux-address-space.gif
TIPS:
代码段就是存储程序文本的,所以也叫文本段,指令指针中的指令就是从这里取得,对应图中的text。这个段是可以被共享的,比如在Linux下开了两个vim编辑文本,那么一般来说这两个vim是共享一个代码段的,但是数据段不同(这有点类似c++中类的不同对象共享相同的成员函数)。代码段的特点是:可读可执行不可写。

数据段是存储数据用的,包括初始化的数据和未初始化的数据(BSS段)两部分,对应图中的data和bss。data一般存放静态非零数据和全局非零数据;bss(Block Started by Symbol)段主要存放未初始化的静态数据和全局数据。数据段的特点是:可读可写不可执行。

堆是给动态分配内存使用的,malloc等函数分配的内存就是在这个区域里的。堆的特点是:可读可写可执行。

首先,execve(2)负责为进程的代码段和数据段建立映射,而真正的将代码段和数据段的内容读进内存是由系统的缺页异常处理程序按需完成的。另外,execve(2)还会将bss段清零,这就是为什么未赋初值的全局变量和静态变量的初值为零的原因。
进程的用户空间的最高位置是用来存放程序运行时的命令行参数和环境变量的,在这段地址的下方和bss段的上方还留有一个很大的“空洞”,而作为进程动态运行环境的堆栈(stack)和堆(heap)就栖身其中,其中堆栈向下生长,堆向上生长。
知道了堆栈在进程地址空间中的位置,下面来看一看堆栈中存放了什么。实际上堆栈中存放的就是与每个函数(调用)对应的堆栈桢(frame,也叫活动记录)。当函数调用发生时,新的堆栈桢被压入堆栈;当函数返回时,相应的堆栈桢从堆栈中弹出。典型的堆栈桢如下图所示:

frame.gif

堆栈桢的顶部为函数的实参,下面是函数的返回地址以及前一个堆栈桢的指针,最下面是分配给函数的局部变量使用的空间。一个堆栈桢通常有两个指针,其中一个称为堆栈桢指针,另外一个成为栈顶指针。前者所指向的位置是固定的,而后者所指向的位置在函数运行过程中可变。因此,在函数中访问实参和局部变量时,都是以堆栈桢指针为基址,再加上一个偏移。从上图可以看出,实参的偏移为正,局部变量的偏移为负。


Intel i386体系结构上,堆栈桢的实现

下面是一个简单的C程序及其编译后生成的汇编程序:

int function(int a, int b, int c)
{
        char buffer[14];
        int     sum;
        sum = a + b + c;
        return sum;
}
void main()
{
        int     i;
        i = function(1,2,3);
}
1   .file   "example1.c"
2     .version    "01.01"
3 gcc2_compiled.:
4 .text
5     .align 4
6 .globl function
7     .type    function,@function
8 function:
9     pushl %ebp
10     movl %esp,%ebp
11     subl $20,%esp
12     movl 8(%ebp),%eax
13     addl 12(%ebp),%eax
14     movl 16(%ebp),%edx
15     addl %eax,%edx
16     movl %edx,-20(%ebp)
17     movl -20(%ebp),%eax
18     jmp .L1
19     .align 4
20 .L1:
21     leave
22     ret
23 .Lfe1:
24     .size    function,.Lfe1-function
25     .align 4
26 .globl main
27     .type    main,@function
28 main:
29     pushl %ebp
30	movl %esp,%ebp
31     subl $4,%esp
32     pushl $3
33     pushl $2
34     pushl $1
35     call function
36     addl $12,%esp
37     movl %eax,%eax
38     movl %eax,-4(%ebp)
39 .L2:
40     leave
41     ret
42 .Lfe2:
43     .size    main,.Lfe2-main
44     .ident  "GCC: (GNU) 2.7.2.3"
frame-example.gif

参考文档