0%

C内存管理

C程序存储空间布局

  • 正文段(Text Segment),由CPU执行的机器指令部分,通常是只读并共享的,即使频繁执行的程序(文本编辑器,编译器,Shell等等)
  • 初始化数据段(数据段,Data Segment),包含了程序中明确赋予初始值的变量,例如在所有函数体外定义int max = 99;
  • 未初始化数据段(bss, block started by symbol 由符号开始的块),内核会在程序执行之前将包含在bss段的变量初始化为0或者空指针。例如在所有函数体外定义long sum[100];,sum存放在未初始化数据段中。
  • 栈,自动变量以及每次函数调用时需要保存的信息都存放在栈中。每次函数调用时,返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。新调用的函数会在栈上分配自动变量和临时变量,这种机制使得递归函数可以正常工作。每次函数调用它自身,都会在栈中创建一个新的栈帧,栈帧中的变量不会相互影响。
  • 堆,通常在堆中动态分配内存,堆位于未初始化数据段和栈之间

内存布局

共享库

共享库中包含了多个可执行文件需要的库,在内存中只维护一个副本。这会减小可执行文件的大小,但是会增加一些运行时开销,这种开销产生于程序第一次执行或者共享库函数第一次被调用时。共享库的另一个优势在于,更新库函数版本时不用重新链接每个使用共享库的程序。

存储空间分配

1
2
3
4
5
6
# include<stdlib.c>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(size_t *ptr, size_t newsize);

void free(void *ptr);

这三个分配函数返回的指针一定是适当对齐的,使得它可以用于任何数据对象。alloc函数的返回值都是void *,当把这些函数返回的指针赋予一个不同类型的指针时,不需要显式地执行强制类型转换。

free释放ptr指向的存储空间,被释放的空间通常被送入可用存储区池,可被malloc函数再次分配。

realloc函数可以增减以前分配的存储区长度,如果该存储区后有足够的空间可以扩充,则可以在原存储区位置上向高地址方向扩充,无需移动任何原先的内容;如果在原存储区后没有足够的空间,则realloc分配另一个足够大的存储区,将当前存储区的内容复制到新分配的存储区,然后释放原存储区。

这些函数通过sbrk系统调用实现,该系统调用可扩充(或缩小)进程的堆。虽然sbrk可以扩充或缩小进程的存储空间,但是大多数malloc和free的实现都不缩小进程的存储空间。释放的空间可供以后再分配,但将它们保存在malloc池中而不返回给内核。

大多数实现分配的存储空间比要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度、指向下一个分配块的指针等。如果超过分配块起始位置或结束位置写,会修改分配块的管理信息,这种错误是灾难性的并且很难发现。

另外的致命性错误包括:释放一个已经释放的块;释放并非由malloc函数创建的指针。如果一个进程调用malloc函数,但是忘了调用free,那么该进程占用的存储空间会持续增加,这被称为泄露(leakage)。如果不调用free返回不再使用的内存,进程的地址空间会不断增长,直至不再有空闲空间。此时由于过度的换页开销,性能会下降。

参考资料

《Unix环境高级编程(第三版)》
How are the size of the stack and heap limited by the OS?