**摘要:**本文系统梳理了C语言程序运行时的五大内存分区(代码段、只读常量区、.data段、.bss段、栈、堆)及其存储内容与生命周期,并深入探讨了全局/静态/局部变量的存储位置、栈与堆的增长方向与溢出原因、malloc/calloc/realloc的区别、malloc底层依赖的brk与mmap系统调用机制及其内存碎片问题,最后解释了free后置空指针的必要性、野指针与空指针的区别以及内存泄漏的概念。
1.详细说一下C语言程序运行时五大内存分区?每个区都存放什么?生命周期是什么?
C语言逻辑上五大内存分区:代码段(.text),只读常量区,全局初始化数据区(.data),未初始化bss段,栈,堆(有些是将.data和.bss区合并起来共叫静态数据区);
代码段.text(低地址):存放二进制机器指令,只读;生命周期:整个程序运行期间
只读常量区:字符串常量,const修饰只读变量;程序整个运行期间都有效
.data(初始化全局数据段):已经初始化的全局变量和静态全局变量/局部变量;程序启动就在,进程结束的时候释放。
.bss段:未初始化全局变量,初始化为0的全局变量;程序启动内核自动清零,进程结束自动释放
栈区(stack):局部变量,函数形参,函数返回地址;由操作系统自动管理,函数调用开辟,函数结束的时候自动释放;生命周期随函数的作用域
堆区(heap):malloc/calloc/realloc手动申请内存;手动free释放,进程结束后操作系统收回,生命周期由程序员控制。
2.全局变量:static静态局部表变量,static静态全局变量,普通局部变量分别存放在那个内存分区?
static int a;静态局部变量,未初始化-->.bss
static int a=0;静态局部变量,初始化为0->.bss
static int a=100;静态局部变量,初始化为非0->.data段
普通局部变量存放在栈区
3.栈的增长方向是向上的还是向下的?linux下的栈默认大小是多少?什么情况下栈会溢出?
栈:向低地址方向增长(向下);堆:向高地址方向增长(向上),
linux下的大小默认为:8MB,可以用ulmit -s查看指令。windows默认为1MB;
栈溢出的结果的俩种场:
函数内部定义过大的局部数组/局部变量,栈帧空间会被占满
无限调用递归也会导致这样,每一次函数调用都会使栈不断地被压缩,栈帧不断地积累,耗尽栈空间
4.malloc,calloc,realloc三者的区别是什么?
malloc专门申请size字节堆内存,不会初始化内存,内存里面是随机脏值,申请成功返回首地址否则返回null;
calloc申请num个元素每个元素占size个字节,总大小num*size。申请完后会把全部的内存置0;成功返回地址失败返回null;
realloc 对已经分配好的堆内存重新调整大小。如果原内存后方堆空间足够;原地扩容,不拷贝数据,返回原来的指针。
如果空间不够:在堆上重新找一片满足new_size的新内存,把旧内存拷贝过来释放旧内存,返回新地址
5.malloc底层依赖那俩个系统调用?brk和mmap有什么区别?
linux下的glibc的malloc底层依靠俩个系统调用:brk,mmap
sbrk是相当于堆brk简单的分装。堆有一个边界指针叫做program break。
推过移动堆顶指针,扩大或缩小堆的连续地址空间,申请小块内存(默认小于128kb)用brk
属于主堆区域 ,free之后内存不会立马还给操作系统,malloc会缓存这块空闲内存,后续malloc可以复用,减少系统调用
容易产生内存碎片;只有堆最顶端的空闲时才能收缩把内存还给内核。
我讲一下为什么会产生内存碎片
堆底->[块a][块b][块c]<-堆顶(program break)
然后我们将free(块b),中间被释放掉了,堆顶指针用不了了因为块c还在使用,所以其实块b空闲了,这片空间还是没有还给内核,只是被标记为空闲
下次分配也不一定会用到块b,假设申请了一块比块b大的内存空间,malloc会申请一块比块b还要大的空间,就只能在堆顶申请一段新空间
堆底->[块a][块b][块c][块d(新申请)]<-堆顶(program break)
反复这样运行的话就会导致释放了但是没用上的空闲块,堆顶却一直在涨,这就叫外部碎片,总空间确实不少,但都是些零散的小件,凑不出连续的空间
那这里又有一个问题为什么mmap不会有这种情况呢?
因为大于128kb的内存,glibc会用mmap将单独映射一块匿名内存
free的时候是直接释放的,不影响堆顶指针,代价是mmap系统调用,频繁这样会导致性能的开销,所以小块的直接就用brk进行复用了
mmap(匿名私有映射)
申请大块内存(>=128kb),malloc不使用主堆,调用mmap,在进程虚拟地址的共享映射区开辟一块独立,和堆不连续的内存。
free的时候调用mmap,直接把内存还给操作系统,不会产生堆碎片,
每次mmap都会陷入内核,系统调用开销更大,内存按页4kb对齐
6.free之后为什么要将指针置null?什么是野指针?野指针和空指针有什么区别?
free之后只是告诉操作系一同要回收这片空间,指针变量本身并没有被销毁,指针仍然保存着原有的内存地址,此时p就成了野指针如果被进行二次free或者进行解引用就会产生未定义行为,置空值后是安全的,解引用会导致段错位,方便调式出错误。
野指针:指针变量存储非法无效的内存地址有三种场景
指针未初始化,存在随即垃圾值。 free之后指针未置空就和上面说的情况是一样的
保存函数局部变量的地址,函数退出导致,局部变量的地址已经销毁然后指针还指向这已经销毁的地址会导致这种情况
空指针;指针明确赋值为null,地址值为0,代表不知向任何有效的内存,free空指针,安全
对空指针解引用后是安全的,解引用会导致段错位,方便调试。
规避野指针就是直接将指针初始化的时候置空,在没有明确指向的时候
free后直接置空, 不要返回局部变量的地址,不要用指针接受已经销毁的栈变量地址
使用指针的时候进行判空处理,在进行解引用。
free之后为什么要将指针置null 其实这种场景也叫做悬空指针
我在这里补充一下内存泄漏:堆上面malloc申请内存,使用完毕没有free释放,这块内存程序还持有,但是不会再使用,程序运行期间内存不断地被占用,内存越来越少