动态内存分配
在谈到动态内存分配之前我们就要谈谈C语言中的内存分配了:栈区 堆区 静态区。
栈区(stack):局部变量、函数的形参存储(自动,连续的内存)出了作用域内存释放。
静态区(static):全局变量、static(关键字)修饰的静态变量存储,在程序的整个生命周期都存在,程序结束,内存自动释放。
堆区(heap):动态存储(非常大的内存池,非连续分配)可以自己分配任意大小的空间(不要找出最大空间范围),但要及时释放(手动),如果在变量使用结束时释放内存失败(未释放)或程序不结束的话会造成内存泄漏,即内存仍然被占用,不能被其它进程获取。
动态内存分配的时候就要自己分配空间(malloc calloc realloc)自己使用,用完自己释放掉(free)那么接下来就开始动态内存分配咯!
free
free 函数无返回值,参数是一个指针变量,该指针变量指向的空间正是你动态开辟的空间。
如果该指针变量存的是空指针(NULL),则free函数释放该空间时啥都不做。
举例就和下面的开辟空间函数一起了
malloc
认识
该函数的功能就是开辟一个空间,参数是你要分配的空间大小(字节为单位),返回值是一个指针(任意类型),如果成功开辟好空间就返回该空间的起始地址,如果开辟空间失败就返回空指针(NULL)。
举例
// 博主使用的是 x64 环境,所以地址看着会长一些。
提一点:如果开辟空间为0的话,标准并未定义该情况,产生的效果取决于编译器。
使用(浅用)
calloc
认识
该函数的功能是开辟一个已经初始化为0的空间,返回值和 malloc 一样,参数一个是元素的个数,另一个是每个元素的大小。
举例使用
calloc 和 malloc 函数的区别:
- 参数不同
- malloc 在堆区开辟空间时不会初始化,calloc 在堆区开辟空间时会将每个字节初始化为0。
realloc
认识
该函数的功能是改变已有的动态内存块的空间大小(也许是开辟一个新的空间),返回值也是一个指针,参数一是要改变的已有动态内存空间,参数二是最终的空间大小。
举例
int main()
{int* p = (int*)malloc(3 * sizeof(int));assert(p);int i = 0;for (i = 0; i < 3; i++)//赋值{p[i] = i + 1;}//想要开辟的空间不够用了,要扩容到5个int* q = (int*)realloc(p, 20);assert(q);//判断空间是否扩容成功if (p != q)//不相等的话{p = q;//将q的值赋给p}//赋值打印for (i = 3; i < 5; i++){p[i] = i + 1;}for (i = 0; i < 5; i++){printf("%d ", p[i]);}free(p);p = NULL;return 0;
}
这里可以看出 realloc 在扩容时,同时将原数据也一并拷贝了过来,所以我后面对扩容的新部分赋值后再打印时,原数据也过来。
剖析
不知道你是否会有疑问,为啥这里要赋值呢?
第一种情况下:
- realloc 会找更大的一块可以存的下总大小的空间。
- 将原空间的数据拷贝过来。
- 释放原来开辟的空间。
- 返回新地址。
- 而第二种情况则返回旧的地址。
动态内存的常见错误
1.对空指针解引用
执行此代码时就会报警告 “取消对 NULL 指针“p”的引用” 所以我们在动态开辟内存之前一定要判断一下是否开辟成功,所以我们会选择断言一下,或用 strerror(errno)来判断。
2.越界访问
此时造成越界访问,编译时程序会崩掉。
3.对非动态开辟内存的释放
此时的程序也是崩了,提一点数组名是首元素地址(常量),不可修改,而指针变量指向的地址属于变量,是可以修改的。
4.使用 free 释放动态内存的一部分
这里开辟的空间首地址是p,而p++了一下,指向了下一个整型的地址,所以这就属于部分动态内存的释放,同样报错的,所以我们在使用动态内存空间时,最好不要改变首地址的指向。
5.对同一块动态内存的反复释放
但是如果你将动态开辟的地址置为空指针之后再次释放空指针此时就没问题
6.动态内存忘记释放(内存泄漏)
当使用动态内存开辟空间时,使用完之后忘记释放的话或者程序不结束的话就会造成内存泄漏,也就是该内存空间被占用,即申请空间空间不使用,不能被其他进程获取,浪费该部分的空间,导致程序运行速度减慢甚至系统崩溃等严重后果