一个C 程序的典型内存布局由以下几部分组成,
文本段也叫代码段,包含编译程序的机器码。通常文本段是可共享的,因此对于频繁执行的程序(例如文本编辑器、C 编译器、shell 等),只需要在内存中保留一个副本。可执行目标文件的文本段通常是只读段,以防止程序被意外修改。
初始化数据段存储所有预先初始化的全局变量、静态变量、常量和外部变量(用 extern
关键字声明的变量),注意这部分存储的变量都是由程序员显式初始化过的,而不是由编译器初始化的。该段可以进一步分为只读初始化数据段和读写初始化数据段,
#include <stdio.h> /* global variable stored in Initialized Data Segment in read-write area*/ char c[]="rishabh tripathi"; /* global variable stored in Initialized Data Segment in read-only area*/ const char s[]="HackerEarth"; int main() { static int i=11; /* static variable stored in Initialized Data Segment*/ return 0; }
在程序开始执行之前,该段中的数据被初始化为算术 0。未初始化的数据从数据段的末尾开始,包含所有初始化为 0 或在源代码中没有显式初始化的全局变量和静态变量。
bss - block started by symbol
#include <stdio.h> char c; /* Uninitialized variable stored in bss*/ int main() { static int i; /* Uninitialized static variable stored in bss */ return 0; }
堆是通常发生动态内存分配的段。当需要使用 malloc
和 calloc
函数分配更多内存时,堆会向上增长。 堆区由进程中的所有共享库和动态加载的模块共享。
malloc
和calloc
都是c
语言中的动态内存分配的函数,此外c语言中还可以使用realloc
函数重新分配内存(重新分配的内存与原内存相比,可大可小)。在c++中动态分配内存不是使用函数进行的,而是使用关键字new
.
#include <stdio.h> int main() { /* memory allocating in heap segment */ char *p=(char*)malloc(sizeof(char)); return 0; }
栈区用于存储所有局部变量,用于向函数传递参数以及函数调用结束后要执行的指令的返回地址(这样函数return
之后,能够接着之前的指令继续进行)。局部变量具有定义它们的块的作用域,它们是在控制进入块时创建的。所有递归函数调用都添加到堆栈中。
正是因为栈区的以上特点,我们可以看出在以下情况下,会发生栈溢出
- 局部变量太大 (这个大可能是真的有比较大的结构体变量,类对象,局部数组,或者太多的局部变量等等)
- 函数调用深度太深,这样我们的要返回执行的指令地址就要不停的压栈
- 同理,如果递归深度太深,也会造成压栈太多,从而栈溢出
要解决栈溢出的问题,我们的对应的方法是,将大的对象放在堆上;避免过深的调用;递归少用(如果必须要用,那一定要做深度限制)
栈和堆通常位于进程虚拟地址的两端,然后使用时不断的向中间靠拢,当两者相遇时,也就空间耗尽了。
以上80%的内容出自下边的文章,在翻译时做了部分修改,并且加入了自己的一些理解,若有兴趣可读原文。
https://www.hackerearth.com/practice/notes/memory-layout-of-c-program/
在windows下可以使用size命令查看程序的内存布局,
size .\VRS.exe
我们将得到以下返回值,
text data bss dec hex filename 615002 8704 0 623706 9845a .\VRS.exe