1.为什么存在动态内存分配
首先,常规的内存开辟就是创建变量或者数组
int i = 0;//在栈空间上开辟4个字节 int arr[10] = { 0 };//在栈空间上开辟40个字节
这两种开辟空间的方式有两个特点:
1. 空间开辟大小是固定的(不可再次修改) 2. 数组在声明时,必须给定数组的大小,开辟的内存在编译时进行分配
这两种开辟内存的方式太过简单,有时不一定能满足自己编程的需求,需要更加高级的内存开辟方式,便可以试试动态内存开辟的方式
2.动态内存函数
C语言提供一定数目的动态内存开辟的函数
2.1malloc
函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
1. 如果开辟成功,则返回一个指向这块空间的指针 2. 如果开辟失败,则返回一个NULL指针,所有 malloc 的返回值必须 要进行检查 3. malloc函数并不知道所开辟空间的类型,返回值的类型是 void* 由使用者在使用过程决定 5. 如果参数 size 为 0,malloc的执行结果是未定义的,取决于编译器
2.2free
既然有开辟内存的函数,那就一定存在释放内存的函数。
C语言就提供了一个释放内存函数 free,专门用来做动态内存的释放和回收的。
free 函数是用来释放动态开辟的内存
1. 如果参数 ptr 指向的空间不是动态开辟的,则 free 函数的执行 结果是未定义的,结果取决于编译器 2. 如果参数 ptr 指向的是 NULL 指针,则函数不会执行任何操作
#include<stdio.h> #include<stdlib.h> #include<errno.h> int main() { //动态内存开辟 int* p = (int*)malloc(40); //对malloc返回值进行检查,若是NULL则打印错误原因 if (p == NULL) { printf("%s\n", strerror(errno)); return; } //使用 //初始化 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } //打印 for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } //释放空间 free(p); //将指针设置成空指针,避免造成野指针 p=NULL; return 0; }
如果在程序结尾没有加上free
并不是表示内存空间不进行回收,而是当程序退出时,系统会自动回收所开辟的空间,当然最保险的方式还是加上free函数
图形展示如下
2.3calloc
函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
1. 函数的功能是开辟num个大小为size的的元素的空间,并将空间的 每个字节都初始化为0 2. calloc==malloc+memset
例如
#include<stdio.h> #include<stdlib.h> #include<errno.h> int main() { //动态内存开辟 int* p = (int*)calloc(10, sizeof(int)); //对calloc函数的返回值进行检查,若是NULL则打印错误原因 if (p == NULL) { printf("%s\n", strerror(errno)); return; } //使用 //打印 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } //释放空间 free(p); //将指针设置成空指针,避免造成野指针 p=NULL; return 0; }
初始化结果
打印结果
图形展示如下
如果对于申请的内存空间的内容要求初始化,那么使用 calloc函数更加方便
2.4realloc
realloc函数可以使动态内存管理更加灵活变通,在编写代码时,总会出现申请的空间太小,或者申请的空间太大,为了更加方便使用,就需要对内存进行灵活的调整。
1. ptr 是待调整的内存地址 2. size 是调整之后的大小 3. 返回值是调整之后的内存起始位置
int main() { //动态内存开辟 int* p = (int*)calloc(10, sizeof(int)); //对calloc函数的返回值进行检查,若是NULL则打印错误原因 if (p == NULL) { printf("%s\n", strerror(errno)); return; } int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } //扩展容量 int* ptr = NULL; ptr = (int*)realloc(p, 80); if (ptr != NULL) { p = ptr; } free(p); p = NULL; return 0; }
realloc函数在调整内存空间时存在两种情况
情况1.原有空间后面由足够大的空间
如果需要扩展内存就直接在原有的内存之后追加空间,原来空间的数据不发生变化
情况2.原有空间之后没有足够大的空间
如果需要扩展内存,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另外找一个合适大小的连续空间来使用,此时函数返回的便是一个新的内存地址。
3.常见的动态内存错误
3.1 对NULL指针的解引用操作
int main() { int* p = (int*)malloc(20); *p = 20; free(p); p=NULL; return 0; }
这里的 p 有可能是空指针(NULL),直接解引用非常危险
对此可进行如下修改
int main() { int* p = (int*)malloc(20); if (p == NULL) { return; } *p = 20; free(p); p = NULL; return 0; }
3.2 对动态开辟空间的越界访问
int main() { int* p = (int*)malloc(20); if (p == NULL) { printf("%s\n", strerror(errno)); return; } //访问 int i = 0; //这里就出现数组越界的情况 for (i = 0; i <= 5; i++) { *(p + i) = i; } free(p); p = NULL; return 0; }
运行结果如下
3.3 对非动态开辟内存使用free释放
int main() { int i = 0; int* p = &i; //..... //..... free(p); p = NULL; return 0; }
free只能释放动态开辟的内存,所以肯定会出现错误的。
运行如下
3.4 使用free释放一块动态开辟内存的一部分
int main() { int* p = (int*)malloc(20); if (p == NULL) { return; } //使用 int i = 0; for (i = 0; i < 5; i++) { *p = i; p++; } //释放 free(p); p = NULL; return 0; }
因为p不再指向动态内存的起始位置,所以肯定会出现错误
3.5 对同一块动态内存多次释放
int main() { int* p = (int*)malloc(20); //...... free(p); //..... free(p); return 0; }
重复释放也会出现错误
3.6 动态开辟内存忘记释放(内存泄露)
void test() { int* p = (int*)malloc(20); //..... int i = 0; scanf("%d", &i); if (i == 1) { return; } free(p); p = NULL; } int main() { test(); return 0; }
如果 i==1,程序会直接跳出 test函数,从而造成动态开辟的内存没有得到释放,电脑本身也找不到这块内存在何处,最终造成内存泄漏。