内存管理
内存分区
C语言中,定义了四个内存空间:代码区、全局变量与静态变量区(静态存储区)、局部变量区即栈区、动态存储区即堆区。
-
代码区:主要存放程序中的代码,属性为只读。(也叫.text段)
-
全局变量与静态变量区:也称静态存储区,该块内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如:全局变量、静态变量、字符串常量。当程序结束的时候才释放该块内存。
该块内存也可分为以下几个分区:
data段—>存放初始化过的静态变量、全局变量等
bss段—>存放未初始化过的静态变量、全局变量等,一般会被编译器默认初始化为0。
rodata段—>即只读存储区,用来存放字符串常量、const修饰的变量等 -
栈区:函数内局部变量的存储区域,函数执行完后其会被自动释放,函数调用时才给分配内存。系统会自动帮助我们申请和释放,但缺点是栈的空间有限,在Linux系统中栈的空间容量为8M。(和动态内存分配不同这个内存不需要程序员自己去管理,我们只需要拿来用,操作系统会帮我们去管理该块内存)
-
堆区:有些对象只有在程序运行时才能确定存储空间,无法为他们预先分配空间,只能在程序运行时才能为他们分配内存。所以也叫动态分配,动态内存的生存期有程序员决定。(其实就是你何时malloc以及何时free)
一图胜千言,为了彻底说清楚这个问题,专门画了张图,下图展示了一个可执行程序ELF格式的构造:
我们知道,源代码编译成程序,程序是放在硬盘上的,而非内存里!只有执行时才会被复制到内存中!我们来看看程序结构,ELF是是Linux的主要可执行文件格式。
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)
-
Program header描述的是一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小
-
Section头表(section header table)包含了描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字,大小,等等信息。相当于 索引!
-
正文段上面是常量区,常量区上面是全局变量和静态变量区,二者占据的就是初始化的数据和未初始化的数据那部分;
-
再上面就是堆,动态存储区,这里是上增长;
-
堆上面是栈,存放的是局部变量,就是局部变量所在代码块执行完毕后,这块内存会被释放,这里栈区是下增长;
最终程序文件的大小由.text,.data,.rodata决定,如此我们在对自己的code进行内存优化的时候就知道从哪里去入手了。
动态内存
- 只有动态内存才能分配和释放
-
动态内存的申请
void *malloc(size_t size);
- 该函数只关心申请内存的大小
- 申请的是一块连续的内存,返回申请的内存的首地址,若失败返回NULL。(记得要进行出错判断)
- 该函数返回的是void *类型的指针,所以使用时需要进行强转。
- 需要显示初始化,堆区不会自动在分配时做初始化的(包括清零),所以申请时需要显示的初始化。
-
动态内存的释放
void free(void *ptr);
- 需要提供内存的起始地址。
- 若分配了空间却没有释放,称为内存泄漏。即你的内存会越用越少。
- malloc应与free配对使用。
- 不允许重复释放
- free只能释放堆区空间
野指针的形成原因与解决办法
- 野指针是指指向垃圾内存的指针,而不是NULL;
- 指针P被free掉了,却没有将其置为NULL。free函数只是将指针所指向的内存释放掉了,使得内存成为自由内存,但是并没有把指针本身的内容清除掉,所以指针仍指向已经释放的动态内存。这是很危险的。
- 没有及时初始化指针。
- 指针操作超越了变量的作用域范围。如数组越界。
- 不要返回指向栈指针内存的指针,因为返回时栈内部才能已经被释放掉了,指针不能再继续访问该块内存。