我们上节说了函数调用的时候,首先函数是被线程执行的,这个线程要执行函数调用的话必须要有内存分配,内存分为两块,一块称为堆,一块称为栈。每个线程都会有自己的栈内存,栈内存是个大整块,调用的时候通过BP或者SP这两个寄存器来维持当前函数需要操作哪块内存,当你都操作完了以后,直接来调整BP或者SP寄存器的位置就可以把你所调用函数的所分配的栈桢空间释放掉。这个释放和在堆上释放是不一样的,因为这里释放后内存完全可以用来干别的事情。但是栈上的内存释放了以后那个内存还在,因为整个栈内存是个整体。这就是整个一大块,我们只不过就是调用时候通过两个寄存器来确定当前操作的时候在这一大块中操作哪一个区域,所以这是有很大区别的。
栈上内存用BP和SP来操作一整块内存的一个区域,用完之后把SP寄存器指回去,那块空间接下来调用其它函数时候进行复用。也就是你的搞明白,首先整个栈内存是一大块,是一整块,它没有说释放某块内存这样的一个说法。除非就有一种可能,就是把整个栈空间释放掉。
但是在堆上我们申请了一段内存,我们不用的时候可以把这块释放掉,因为我们在一个函数里面可以多次调用堆内存分配,然后可以分块释放。栈上没有内存释放这种说法。所以这就有个好处在栈上只需要调整两个寄存器BP、SP的位置就可以来决定这个内存当前是正在用或者说是可以被其它函数调用来覆盖掉。所以有这样一个说法,我们尽可能把对象分配到栈上。因为不需要执行释放操作。因为现场恢复时候只需要调整寄存器,那块内存就变得可复用状态了。但是在堆上你必须要释放,在栈上的效率显然是要高很多。而且栈这种特性就决定了它是有顺序操作的机制,所以它的效率就高很多。那么你在堆上分配时候要么手动释放要么有垃圾回收器来释放。垃圾回收器只管堆上的东西,栈上它是不管的。所以我们在栈上分配的时候,一是效率比较高,第二不会给垃圾回收器带来负担。
我们现在知道了每个函数调用的时候都会在栈上用两个寄存器划出一个区域来存储参数、返回值、本地变量类似这样的一些内容,这个区域我们称之为叫栈桢。那么多级调用时候所有的栈桢串在一起我们称之为调用堆栈。
那么究竟有哪些东西分配在栈上呢?比如说在函数里面x=10
这种东西默认情况下肯定分配在栈上,*p=malloc()
这个时候这东西在堆上还是在栈上呢?这时候实际上有两种东西,第一malloc的确是在堆上分配一个内存空间,这个内存空间分配完了之后得有个指针指向它。所以这地方严格来说有两个东西。第一个是堆上的内存块,还有个指针变量,这个指针变量可能是在栈上。指针本身是个标准的变量,它是有内存空间的,它没有内存空间的话地址怎么写进去,因为我们知道我们可以给指针赋值的,能给它赋值肯定是个对象,没有对地址赋值这样一个说法,地址肯定不能赋值的。所以指针和地址不是一回事。指针是一个标准的变量,里面存了地址信息而已。所以指针和地址完全不是一个东西,不要混合一谈。复合对象是不是分配在堆上也未必,这得看不同的语言对复合对象怎么定义了,比如说结构体算不算复合对象,数组算不算复合对象,默认情况在栈上分配没有问题,当然里面可以用指针指向堆上其它的地址。你别忘了当里面有指针指向别的对象的时候,这个指针本身它依然是在栈上的。比如说我有个复合对象结构体,有个x和一个指针p,指针p指向堆上一个内存对象,堆上内存对象不属于结构体本身的内容。因为只有这个指针属于这个结构体,至于这个指针指向谁和这个结构体没关系,这结构体本身是完全分配在栈上的。只不过结构体里面有个东西记录了堆上的地址信息而已。
接下来了解对象参数究竟怎么去分配的。
C参数复制,返回值
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
__attribute__((noinline)) void