动态内存分配的意义
假如我们创建一个存放一百个元素的数组:
char arr[100] = {0};
如果我们要用这个数组来存储数据的话只能存储100个char型的数据,如果再想要往数组内添加数据的话就会越界。
所以在这个时候有两种方法,第一个方法是再创建一个数组然后将两个数组拼接,第二个方法时用动态内存分配。动态内存分配的快捷,实用性和可操控要比第一种方法强很多,那么究竟强在哪里呢?在这篇博客中我将进行具体阐述。
动态内存分配相关函数
1. malloc
malloc函数用来向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
该函数原型如下:
void* malloc (size_t size);
可以看出,该函数的返回值是void*说明返回类型可以改变,传的参数size作用是向内存申请size大小的内存,用size_t类型可以不用担心申请的内存过大而不能完成申请。
对于开辟内存的结果和操作:
- 如果开辟成功,则返回⼀个指向开辟好空间的指针。
- 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
- 返回值 的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃⼰来决定。
- 如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器
2. free
free函数用来释放动态分配的内存(只能释放动态分配的内存),函数原型如下:
void free (void* ptr);
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
- 如果参数 ptr 是NULL指针,则无事发生。
在了解到malloc和free函数后就可以利用这两个函数进行一个简单的实例代码:
#include <stdio.h> #include <stdlib.h> int main() { int num = 0; scanf("%d", &num); int arr[num] = {0}; int* ptr = NULL; ptr = (int*)malloc(num*sizeof(int)); if(NULL != ptr)//判断ptr指针是否为空 { int i = 0; for(i=0; i<num; i++) { *(ptr+i) = 0; } } free(ptr);//释放ptr所指向的动态内存 ptr = NULL;//是否有必要? return 0; }
该段代码就是实现动态分配,然后释放分配的空间。
3. calloc
calloc 函数也⽤来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
- 函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0.
示例代码:
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)calloc(10, sizeof(int)); if (NULL != p) { int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
示例代码结果:
0 0 0 0 0 0 0 0 0 0
从函数的特性出发,如果我们对函数有初始化的需求的话可以直接使用 calloc 函数来进行内存空间的开辟,效果与 malloc 一样。
4. realloc
realloc 函数实现的是对原本开辟的空间进行调整大小,函数原型如下:
void* realloc (void* ptr, size_t size);
- ptr 是要调整的内存地址
- size 调整之后新⼤⼩
- 返回值为调整之后的内存起始位置。
- 这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动(copy)到新的空间。
- realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有⾜够⼤的空间
情况2:原有空间之后没有⾜够⼤的空间
以上所说两种空间是在内存上的空间,不是当前动态分配到的空间
4.1 原有空间足够大
当原有空间足够大时,我们要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
4.2 原有空间不够
当原有的空间不够时,我们在堆空间上另找⼀个合适大小的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
realloc使用示例代码:
#include <stdlib.h> int main() { int* ptr = (int*)malloc(100); if (ptr != NULL) { //业务处理 } else { return 1; } //扩展容量 //代码1 - 直接将realloc的返回值放到ptr中 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?) //代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中 int* p = NULL; p = realloc(ptr, 1000); if (p != NULL) { ptr = p; } //业务处理 free(ptr); return 0; }
常见动态内存错误
1. 对NULL指针解引用
void test() { int* p = (int*)malloc(INT_MAX / 4); *p = 20;//如果p的值是NULL,就会有问题 free(p); }
2. 对动态开辟空间的越界访问
void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { exit(EXIT_FAILURE); } for (i = 0; i <= 10; i++) { *(p + i) = i;//当i是10的时候越界访问 } free(p); }
3. 对⾮动态开辟内存使⽤free释放
void test() { int a = 10; int *p = &a; free(p);//error }
4. 使⽤free释放⼀块动态开辟内存的⼀部分
void test() { int *p = (int *)malloc(100); p++; free(p);//p不再指向动态内存的起始位置 }
5. **对同⼀块动态内存多次释放 **
void test() { int* p = (int*)malloc(100); free(p); free(p);//重复释放 }
6. 动态开辟内存忘记释放(内存泄漏)
void test() { int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); }
结语
以上就是关于动态内存管理的博客内容。值得强调的是关于 free 掉内存这件事,一个程序如果不在不需要的时候free掉内存的话就会一直占用内存直到程序运行结束。
在我们平时的代码练习中不会有明显的影像,但是在大型程序中,如果内存一直占用,占用的内存不断增多,内存是有限的,不可能一直被占用,当内存爆满时程序就会出现问题了。
所以要注意关于内存空间的释放!