目录
一、动态内存分配是什么?
二、为什么需要动态内存分配?
三、怎么进行动态内存分配?
1. malloc
2. calloc
3. realloc
a. realloc功能解析
b. 内存泄漏和内存块被截断问题
c. 总结
4. free
四、使用动态内存分配常见的问题
【面试题】
一、动态内存分配是什么?
动态内存分配是指在程序执行的过程中,系统根据程序的需要动态地分配或回收存储空间的分配内存的方法。
这种分配方式不像数组等静态内存分配方法那样,需要预先指定要分配的内存大小,且一经分配后大小无法修改,而是由系统按程序的需要灵活地分配和回收内存空间。
二、为什么需要动态内存分配?
在C99引入变长数组前,定义数组时大小不能是变量,这就导致了开辟数组时必须指定数组的大小,且一经开辟,数组的大小再也无法改变。但实际情况下,如实现一个通讯录时,我们往往不知道需要多大的数组,且经常会遇到数组容量不够的情况,这就是静态内存分配的弊端。
早期的解决方案就是使用动态内存分配,也就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行更灵活的分配和回收。
使用动态内存分配的优势:
- 可以灵活分配、回收内存:
与大小固定的数组相比,动态内存分配可以使用malloc、calloc申请分配内存,使用relloc对申请好的内存空间大小进行调整,使用free回收内存。- 可以随时回收,多次利用这部分空间:
静态内存分配的空间,直到整个程序结束才会由系统自动释放。但是动态内存分配的空间需要用户手动释放,所以我们可以在使用完后立马通过free函数空间,使得这块空间能在一个程序中被反复使用,这样也能在一定程度上节省内存空间。- 使用内存中堆区的空间
动态内存分配是在堆上分配空间的,静态内存分配是在栈上分配空间的,所以了解动态内存分配后就能使用堆上的空间了。
三、怎么进行动态内存分配?
C语言中动态内存管理方式是通过四个库函数实现的:malloc、calloc、 realloc、free。这四个库函数都在头文件<stdlib.h>中。
1. malloc
函数声明 | void* malloc (size_t size); |
功能 | 在堆上申请一个大小为size字节的空间。(申请分配动态内存) |
返回值 | 若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。 |
#include<stdio.h>
#include<stdlib.h>int main()
{// 开辟一个动态内存变量//malloc函数的返回值类型为void*所以要强制类型转换int* a = (int*)malloc(1 * sizeof(int));// 检查动态内存是否开辟失败,开辟失败返回NULLif (NULL == a){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存*a = 1;printf("%d\n", *a);// 释放动态内存free(a);a = NULL; //free后指针仍指向原来的空间,所以要置为NULL
}
// 开辟一个动态内存数组int size = 10; // 动态内存数组大小int* arr = (int*)malloc(size * sizeof(int));// 检查动态内存是否开辟失败if (NULL == arr){printf("动态内存开辟失败。\n");return 0;}// 使用动态内存数组(和静态内存数组的使用没有差别)for (int i = 0; i < size; ++i){arr[i] = i;printf("%d ", arr[i]);}// 释放动态内存数组free(arr);arr = NULL;
2. calloc
函数声明 | void* calloc (size_t num, size_t size); |
功能 | 在堆上申请大小为num个size字节的空间,并初始化空间中的内容为0。(分配动态内存) |
返回值 | 若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。 |
// 使用calloc函数开辟一个动态内存变量/数组int size = 10; // 动态内存数组大小,size = 1即为变量int* arrc = (int*)calloc(size , sizeof(int));// calloc函数的返回值类型为void*if (NULL == arrc)// 检查{printf("动态内存开辟失败。\n");return 0;}for (int i = 0; i < size; ++i)// 使用{printf("%d ", arrc[i]); //calloc默认初始化内容全为0}free(arrc);// 释放arrc = NULL;
3. realloc
函数声明 | void* realloc (void* mem_address, unsigned int newsize); |
功能 | 见下方解析。(对申请好的动态内存空间大小进行调整,可以扩展或缩小动态内存。) |
返回值 | 若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。 |
a. realloc功能解析
realloc函数用于对申请好的动态内存空间大小进行调整。它接受两个参数:一个是已分配的动态内存的指针,另一个是要重新分配的大小(单位字节)。
realloc函数首先检查传入的指针是否为空指针。如果是空指针,则等同于调用malloc函数,直接分配指定大小的动态内存,并返回指向该动态内存的指针。
如果传入的指针不是空指针,则realloc函数会尝试重新分配内存块的大小。它会根据传入的新内存块大小newsize和原有内存块的大小oldsize来判断如何重新分配内存:
- newsize = 0,释放原有的内存块,并返回空指针。
- newsize > oldsize,则realloc函数会尝试在指针参数指向原有内存块的上进行扩容:
如果原有内存块有足够的连续空间,直接扩容,并且将传入的指针返回;
如果没有足够的连续空间,先按照指定的大小分配一个新的内存块,然后将原有数据拷贝到新内存块中,而后由系统自动释放原来的内存块,并返回新内存块起始位置的指针;
如果扩容失败,则返回空指针,并且原有内存块的内容保持不变。- newsize < oldsize,则realloc函数会尝试缩小内存块的大小:
它可能会直接使用缩小后的原内存块;
也可能将原内存块中的数据复制到新内存块中,并返回指向新内存块的指针。内容不变,但是多余的数据可能会被截断。
b. 内存泄漏和内存块被截断问题
使用realloc函数时需要小心内存泄漏和内存块被截断的问题:
- 如果realloc函数返回空指针,则说明内存分配失败,原内存块没有被释放,这可能会导致内存泄漏;(realloc失败不会释放原空间)
- realloc函数可能会因为原来位置的内存大小不够,将原有内存块移到新的位置。但是,指向原有内存块的指针仍然指向原内存块,这可能会导致内存泄漏。所以realloc后要将原指针置为NULL。
- 如果新的内存块大小小于原有内存块的大小,则可能会导致多余的数据被截断。
c. 总结
realloc函数用于重新分配内存块的大小,可以扩展或缩小内存块。但是使用时需要注意内存泄漏和数据截断的问题。
// 使用realloc函数调整动态内存的大小// 1.如果传入的是空指针,则相当于使用malloc开辟新空间:int* p1 = (int*)realloc(NULL, 1 * sizeof(int));if (NULL == p1){printf("动态内存开辟失败。\n");return 0;}*p1 = 1;printf("%d\n", *p1);free(p1);printf("p1所指向的空间:%d\n", sizeof p1);// 2.如果新空间需要的大小newsize = 0//则释放原有的内存块,并返回空指针。int* p2 = (int*)malloc(1 * sizeof(int));int* p3 = (int*)realloc(p2, 0);if (NULL == p3)printf("p2, p3所指向的空间将会由系统在合适的时间释放。\n");// 3.指针指向的旧空间大小oldsize < 新空间需要的大小newsize// 可能在原空间上扩容,也可能找一个新空间扩容int* p4 = (int*)calloc(10, sizeof(int));int* p5 = (int*)realloc(p4, 12 * sizeof(int));int* p6 = (int*)realloc(p5, 1000 * sizeof(int));if (p4 == p5)printf("在原空间上扩容。\n");if (p5 != p6)printf("找一个新空间扩容。\n");// 4.oldsize > newsize// 可能使用原空间,也可能找一个新空间int* p7 = (int*)calloc(10, sizeof(int));int* p8 = (int*)realloc(p7, 9 * sizeof(int));int* p9 = (int*)realloc(p8, 2 * sizeof(int));if (p7 == p8)printf("使用原空间。\n");if (p8 != p9)printf("找一个新空间。\n");
4. free
函数声明 | void free (void* ptr); |
| 释放ptr指向的空间,如果ptr是空指针则不做处理;如果ptr指向的空间不是动态内存或是 功能 | 已经被释放的动态内存,则free函数的结果是未知的——可能导致程序崩溃,也可能导致 | 内存泄漏。(回收动态内存) |
返回值 | 无。 |
释放动态内存,并不意味着执行到free函数时立马让该空间的内存全变为0,也不会让指针指向NULL,这只是告诉操作系统可以重新使用该内存块,具体的释放时间由操作系统决定。所以free后要手动将指针置为NULL。这也意味着无法通过指针是否指向NULL来判断空间是否已经被释放。
注意,只有动态申请的堆区内存需要我们主动释放,其它区都由系统管理,强行free可能导致程序崩溃:
四、使用动态内存分配常见的问题
3.1 不检查动态内存是否开辟成功
如果开辟失败,返回值为NULL,不检查就可能会对NULL进行解引用操作。int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
3.2 对动态开辟空间的越界访问
和静态数组一样,因忘记数组大小,导致越界访问。int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (int i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
3.3 对非动态开辟内存使用free释放
强行free非动态内存可能导致程序崩溃int t = 1;int* tp = &t;free(tp);return 0;
3.4 使用free释放一块动态开辟内存的一部分
指向动态内存的指针不再指向起始位置。int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
3.5 对同一块动态内存多次释放
一样可能导致程序崩溃。int* p = (int*)calloc(10, sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}free(p);free(p);return 0;
3.6 动态开辟内存忘记释放(内存泄漏)
最常见的问题:忘记使用free函数释放动态内存空间,如果是一个长期运行的程序,这部分内存空间会因未释放而一直被占用,这就导致程序消耗的内存越来越多。切记: 动态开辟的空间一定要释放,并且正确释放 。
3.7 使用realloc和free后,未将原指针置为NULL
realloc函数可能原位置空间不足,而使用新内存块移到新的位置。但是不会让指向原有内存块的指针不会指向NULL。free函数也不会让指针指向NULL。这可能会导致内存泄漏。所以realloc和free后要将原指针置为NULL。
【面试题】
题目1:
void Test()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
// 2.这里需要free(p2)吗?
free(p3);
}
1. malloc/calloc/realloc的区别?
从参数和函数功能方面进行回答。
2.这里需要free(p2)吗?
答:不需要,使用realloc扩容时,自动释放了p2之前的空间。
题目2:
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
1. 请问运行Test 函数会有什么样的结果?
答:运行Test函数会导致程序崩溃,因为形参只是实参的拷贝,形参的改变并不影响实参,所以str仍指向NULL,这会导致空指针异常。
题目3:
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
1. 请问运行Test 函数会有什么样的结果?
答:不确定,因为在函数GetMemory执行完后,所处空间被系统销毁,p指向的空间也被销毁了,此时str因函数返回值指向了p指向的这片被消毁的空间。所以运行Test函数会产生不确定的结果。
题目4:
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
1. 请问运行Test 函数会有什么样的结果?
答: 可能会导致程序崩溃,也可能会在显示屏上打印world。因为不是执行free函数就立即销毁指针指向的空间,指针也不会被free函数被置为NULL,所以指针仍指向原空间,如果系统还没销毁该空间,那么会在显示屏上打印world,否则会导致程序崩溃。
------------------------END-------------------------
才疏学浅,谬误难免,欢迎各位批评指正。