C语言栈帧的组织

栈帧由以下几种元素组成。

  • 返回地址
    函数完成后要返回的程序内部地址。
  • 局部数据存储
    为局部变量分配的内存。
  • 参数存储
    为函数参数分配的内存。
  • 栈指针和基指针
    运行时系统用来管理栈的指针。

普通C程序员不会关心支持栈帧的栈指针和基指针。不过,理解它们的概念和用法能让你更深入地理解程序栈。

栈指针通常指向栈顶部。基指针(帧指针)通常存在并指向栈帧内部的地址,比如返回地址,用来协助访问栈帧内部的元素。这两个指针都不是C指针,它们是运行时系统管理程序栈的地址。如果运行时系统用C实现,这些指针倒真是C指针。

我们以下面这个函数为例来了解栈帧的创建。该函数传递了一个整数数组和一个表示数组长度的整数。三个printf语句用来打印参数和局部变量的地址:

float average(int *arr, int size) {
    int sum;
    printf("arr: %p\n",&arr);
    printf("size: %p\n",&size);
    printf("sum: %p\n",&sum);

    for(int i=0; i<size; i++) {
        sum += arr[i];
    }
    return (sum * 1.0f) / size;
}

执行上述代码会得到类似下面的输出:

arr: 0x500
size: 0x504
sum: 0x480

参数地址和局部变量地址之间的空档,保存的是运行时系统管理栈所需要的其他栈帧元素。

系统在创建栈帧时,将参数以跟声明时相反的顺序推到帧上,最后推入局部变量,如图所示。在这个例子中,sizearr之前被推入。通常,接下来会推入函数调用的返回地址,然后是局部变量。推入它们的顺序跟其在代码中列出的顺序相反。

栈帧示例

从原理上说,本例中的栈“向上”生长。不过栈帧的参数和局部变量以及新栈帧被添加到了低内存地址。栈的实际生长方向跟实现相关。

for语句中用到的变量i没有包含在栈帧中。C把块语句当做“微型”函数,会在合适的时机将其推入栈和从栈中弹出。在本例中,块语句在执行时被推到程序栈中average栈帧上面,执行完后又弹出。

精确的地址可能会变化,不过顺序一般不变。这一点很重要,因为它可以解释参数和变量内存分配的相对顺序。在调试指针问题时这一点会很有用。如果你不知道栈帧如何分配,这些地址在你看来也毫无意义。

将栈帧推到程序栈上时,系统可能会耗尽内存,这种情况称为栈溢出,通常会导致程序非正常终止。要牢记每个线程通常都会有自己的程序栈。一个或多个线程访问内存中的同一个对象可能会导致冲突,我们将在8.3.1节中讨论这个问题。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程