首先,为什么叫动态分配内存?
因为内存宝贵,存储在静态内存空间的全局变量,常量不能在使用完成之后立即释放,需要随着程序的结束而终止,如果程序规模庞大,就会引起内存泄漏;而动态内存分配可以按需申请内存,用完就将内存归还给操作系统。
全局变量 局部变量
- 全局变量:static修饰的变量 或者 不在任何花括号内的变量
- 局部变量:函数中的变量,随着函数栈帧而创建和消亡,main函数里的变量也是局部变量,只不过它的生命周期和全局变量一样长
- 堆空间:巨大的空间,申请和回收
malloc
用来动态地分配一个独立的指定大小的内存单元,返回一个void类型的指针,可以转化成任意形式的指针。
1 | int *ptr=(int *)malloc(5*sizeof(int)); |
以32位机器为例,int占4字节,如果空间总长度不满20字节,返回NULL指针。
循环为数组动态申请内存
1 |
|
calloc
contiguous allocation
,用于动态分配指定数量、指定类型连续的内存块,和malloc函数相比,calloc是为指定长度的对象,分配能够容纳其指定个数的存储空间。
1 | int *ptr=(int *)calloc(5,sizeof(int)); |
在内存中为 5 个元素分配连续空间,每个元素的大小为整型数。
内存泄漏
内存溢出:OOM Out Of Memory
,程序在申请内存时,没有足够的内存空间供其使用,比如申请一个int型变量,但是让它存了long类型的数。
内存泄漏:Memory Leak
,程序在申请内存后,没有及时释放申请过的内存空间,结果这部分内存空间就像被互斥锁了,无法访问,长此以往,最终会导致内存溢出,内存迟早会被占光。
- 指针重新赋值
1 | char *p=(char *)malloc(4*sizeof(char)); |
p原先所指向的内存空间没有释放变成了孤立的内存。
- 错误的内存释放
1 | char **p=(char **)malloc(sizeof(char *)); |
释放了由 p 指向的内存块,即第一步中使用 malloc 分配的内存。这样做将导致内存泄漏,因为第二步中分配的内存(p[2]即np 所指向的内存)并没有被释放。
- 没有对函数返回的值做正确的处理
1 | char *f(){ |
函数 f 动态分配了一个大小为10的字符数组,并返回其首地址。然后,在函数 f1 中,调用了函数 f,但没有保存或使用其返回的指针,也没有释放由 f 分配的内存。
野指针
- 指针未初始化
1 | int *q; |
- 越界访问
1 | int arr[10]={0}; |
如何避免?
除了避免越界访问,需要对指针指向地址初始化,可以使用malloc分配堆内存,如果分配失败,则返回NULL,使用时,判断指针是否为NULL就可以避免了。
free
既然有动态分配内存,那么也有动态取消分配内存,因为使用函数malloc和calloc分配的内存不会被自行取消分配。
realloc
顾名思义,re-allocated,再次分配,当已经使用malloc或者calloc分配了一部分存储空间,并且已经使用完,需再分配,即使用realloc。
在现有内存空间下扩展存储空间
1 |
|
什么时候使用
在C语言中,当一个函数执行完毕后,其局部变量通常会被销毁,内存被释放。因此,如果你在函数中创建了一个局部变量,并尝试返回指向该局部变量的指针,那么在函数返回后,该指针就会指向一个已经被销毁的内存区域。
为了避免这个问题,你可以使用动态内存分配,比如使用 malloc 函数,来在堆上分配内存。动态分配的内存在调用 free 函数之前一直存在,因此你可以在函数外部访问它。
比如说:
1 |
|
上述代码导致未定义行为,在test函数中,返回一个局部变量a的地址,当test函数执行完毕时,局部变量a超出了其作用域,其占用的内存不再保证有效,所以说main函数尝试解引用会导致未定义行为。
未定义行为意味着程序的行为是不可预测的,它可能在不同的编译器上产生不同的结果;所以说我们使用malloc来在堆上分配内存。
1 |
|
当然也可以使用static关键字修饰局部变量,这样a就存在静态区域,从程序开始到程序结束。
malloc是如何分配内存的
malloc不是系统调用,而是一个库函数,malloc在申请内存的时候,有两种方式向操作系统申请堆内存。
- 方式1:通过
brk()
系统调用从堆分配内存
通过brk函数将堆顶指针向高地址移动,获取新的内存空间。
- 方式2:通过
mmap()
系统调用在文件映射区分配内存
通过mmap系统调用中私有匿名映射的方式,在文件映射区分配一款内存,也就是从文件映射区偷了一块内存。
- 使用场景
malloc()
源码里默认定义了一个阈值:
如果用户分配的内存小于 128 KB,则通过 brk()
申请内存;
如果用户分配的内存大于 128 KB,则通过 mmap()
申请内存;
free之后内存真的释放了吗?
通过brk申请内存,free后堆内存还存在,不会把内存归还给操作系统,而是缓存在malloc的内存池中,待下次使用;如果通过mmap方式申请内存,释放后归还给操作系统。
有没有想过一个问题,free函数只是传入一个内存地址,是怎么直到要释放多大的内存的?
malloc返回给用户态的内存起始地址比进程的堆空间起始地址多了16个字节,这一部分就是用来保存内存块的描述信息,比如该内存块的大小。