本章重点:
介绍malloc ,calloc , realloc 等动态内存分配函数的使用方法与常见的动态内存错误,与讲解几道动态内存分配的笔试题
1. 为什么存在动态内存分配
int val = 20; 在栈空间上开辟四个字节 char arr[10] = {0}; 在栈空间上开辟10个字节的连续空间
以上的内存开辟空间都是在栈上开辟的,而栈区的空间有以下几个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
- 栈区由编译器自动分配释放,由操作系统自动管理,无须手动管理。
- 栈区上的内容只在函数范围内存在,当函数运行结束,这些内容也会自动被销毁。
- 栈区上的地址是由高到底的
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就存在动态内存开辟了。
补充:
- 动态内存分配的空间是在堆区上开辟的
- 栈区上放的是局部变量和函数的形式参数等
- 静态区上放的是全局变量与静态变量(static)
2. 动态内存函数
malloc 和 free
malloc和free是成双成对出现的
malloc和free都声明在 stdlib.h 头文件中。
C语言提供了一个动态内存开辟的函数
void* malloc (size_t size);(size_t无符号整形)
malloc 使用方法与注意事项:
- malloc这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使
用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
- 如果没有free回收空间,系统会到最后自动回收,但是中间如果有大量程序要执行,这一
块空间就一直占用着,(千万不要这样处理)不释放会产生内存碎片,小型程序可以不关注,但是在中大型程序上影响极其深刻
注意:申请的空间刚开始都放的是随机值,如果要参与运算,都要给一个初始值,否则运算的结果肯定是随机值
free函数用来释放动态开辟的内存。
void free (void* ptr);
free使用方法与注意事项:
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- 每次动态内存开辟后,都要记得释放该开辟的内存
- 释放空间后该指针还是指向原来的地址,为防止后面使用这个危险指针,应该释放完后赋
个NULL
- 使用案例
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> int main() { //向内存申请10个整形的空间 int* p = (int*)malloc(40); //如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 if (p == NULL) { //查询那里错误 printf("%s\n", strerror(errno)); } //如果开辟成功,则返回一个指向开辟好空间的指针 else { //正常使用空间 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d\n", *(p + i)); } } //当动态申请的空间不再使用的时候 //就应该还给操作系统 free(p); p = NULL; //释放空间后p还是指向原来的地址,为防止后面使用这个危险指针,使用赋个NULL system("pause"); return 0; }
最终输出结果:
0
1
2
3
4
5
6
7
8
9
calloc
C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。
void* calloc (size_t num, size_t size);
函数的功能与malloc相似
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化
为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为
全0。
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> int main() { //calloc 全部初始化为0 int* p = (int*)calloc(5, sizeof(int)); if (p == NULL) { 查询那里错误 printf("%s\n", strerror(errno)); } else { //正常使用 int i = 0; for (i = 0; i < 5; i++) { printf("%d\n", *(p + i)); } } 当动态申请的空间不再使用的时候 就应该还给操作系统 free(p); p = NULL;//释放空间后p还是指向原来的地址,为防止后面使用这个危险指针,使用赋个NULL system("pause"); return 0; }
最终输出结果:
0
0
0
0
0
所以我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
calloc 和 malloc 的对比:
- 参数不一样
- 都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0
- 如果要初始化,就使用calloc不需要初始化,就可以使用malloc