C语言的内存管理
C语言的内存管理
对于一个C语言程序而言,内存空间主要由五个部分组成代码段(.text)、数据段(.data)、BSS段(.bss),堆(heap)和栈(stack)组成,其中代码段,数据段和BSS段是编译的时候由编译器分配的,而堆和栈是程序运行的时候由系统分配的。布局如下:
在上图中,由编译器分配的地址空间都是在连接(链接)的时候分配的,而运行时分配的空间是在程序运行时由系统分配的。
BSS段:BSS段(Block Started by Symbol)通常是指用来存放程序中未初始化(程序员在创建时没有赋值)的全局变量和静态变量的一块内存区域(可读可写不可执行)。特点是可读写的,在程序执行之前BSS段会自动清0。 (注意:一般的书上都会说全局变量和静态变量是会自动初始化的,为什么会这样呢?变量的初始化可以分为显示初始化(程序员在创建时手动赋值)和隐式初始化(程序员在创建时没有赋值,编译器自动将其初始化为0)。既然程序员未赋值的全局变量和静态变量最后都被自动赋值为0,那么就没必要把每个0都存储起来,把这些未初始化的变量统一存在BSS段,程序运行前统一将BSS段清0即可。这样做可以节省磁盘空间,同样一个程序如果全局变量和静态变量在创建时未初始化,那么编译器将其转化成的二进制可执行文件就会小很多。这是BSS的主要作用)。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量、字符常量的一块内存区域(可读可写不可执行)。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中。
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域(可读可执行不可写)。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中。
以上BSS、数据段、代码段都是编译器在程序运行前就分配好的。如果代码中的全局变量和静态变量在创建时不立即初始化,那么它们会被存储在BSS段,BSS段会在程序运行前统一清0,这样程序的可执行文件就会小很多。
下面两个堆和栈是在程序运行时,动态分配的,不是由编译器管理。
堆(heap):堆是用于存放进程运行中被动态分配的内存段(即对象),它的大小并不固定,可动态扩张或缩减(可读可写可执行)。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量(可读可写可执行),也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。注意:栈空间是向下增长的,每个线程有一个自己的栈,在linux上默认的大小是8M(ios上线程默认是1M),可以用ulimit查看和修改。
栈系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而堆是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。
Note: 一般编译器和操作系统实现来说,对于虚拟地址空间的最低(从0开始的几K)的一段空间是未被映射的,也就是说它在进程空间中,但没有赋予物理地址,不能被访问。这也就是对空指针的访问会导致crash的原因,因为空指针的地址是0。至于为什么预留的不是一个字节而是几K,是因为内存是分页的,至少要一页;另外几k的空间还可以用来捕捉使用空指针的情况。