程序栈
说到栈帧,不得不先说说程序栈。
程序栈是支持函数执行的内存区域,通常和堆共享。程序栈通常占据内存区域的下部,而堆用的是上部。
程序栈存放栈帧,栈帧有时候也称为活跃记录或活跃帧。栈帧存放函数参数和局部变量。堆存放动态内存。
调用函数时,函数的栈帧被推到栈上,栈向上长出一个栈帧。当函数终止时,其栈帧从程序栈上弹出。栈帧所使用的内存不会被清理,但是最终可能会被推到程序栈上的另一个栈帧覆盖。
动态分配的内存来自堆,堆向下生长。随着内存的分配与释放,堆中会布满碎片。尽管堆是向下生长的,但是这只是大体方向,实际内存可能位于堆上的任意位置。
栈帧的组织
栈帧由下面几个元素组成:
返回地址:函数完成后要返回的程序内部地址
局部数据存储:为局部变量分配的内存
参数存储:为函数参数分配的内存
栈指针和基指针:运行时系统用来管理栈的指针
栈指针通常指向栈顶部。基指针通常存在并指向栈帧内部的地址,比如返回地址,用来协调访问栈帧内部的元素。这两个指针都不是C指针,他们是运行时系统管理程序栈的地址。
下面这个函数给出实例:
float average(int *arr, int size){ int sum; printf("arr: %p\n",&arr); printf("size: %p\n",&size); priintf("sum: %p\n",&sum); for(int i = 0;i < size;i ++){ sum +=arr[i]; } return (sum *1.0f) / size; } //output //arr: 0x500 //size: 0x504 //sum: 0x480
其中参数地址和局部变量之间的空档,保存的是运行时系统管理栈所需的其他栈帧元素。
系统在创建栈帧时,将参数以跟声明时相反的顺序推到栈上,最后推入局部变量。在这个例子中,size在arr之后被推入。通常,接下来会推入函数调用的返回地址,然后是局部变量。推入它们的顺序和在代码中的顺序相反!
从原理上说,本例中的栈向上生长。不过栈帧的参数和局部变量以及新栈帧被添加到了低内存地址。栈的实际生长方向和实际相关。
for语句中的i没有包含在栈帧中。这是因为C语言中把块语句当成“微型”函数。
将栈帧推到程序栈上时,系统可能会耗尽内存,这种情况叫做栈溢出,通常会导致程序非正常终止。要牢记每个线程都会有自己的程序栈,一个或多个线程访问内存中同一个对象可能会造成冲突!