目录
引言
在编写高效且灵活的C语言程序时,内存管理扮演着至关重要的角色。静态分配虽然简单易用,但在面对变长数据结构和复杂需求时,动态内存管理提供了更强的弹性。通过合理地申请、使用和释放内存,我们不仅可以优化资源利用,还能避免潜在的内存泄漏问题。本篇博客将带你全面了解C语言中的动态内存管理机制,掌握相关的核心技术与实践技巧。
一、为什么要有动态内存分配?
因为灵活。现实情况中,有很多的不确定因素,不确定的数量,不确定的大小,如果设置的容量小了不够用,大了又会浪费空间,这时候就绪需要用到动态内存来灵活的设置容量大小。
二、malloc
该函数包含在 <stdlib.h> 中,它的原型如下:
void* malloc(size_t size);
功能:向内存的堆区申请一块连续可用的空间
返回:如果申请成功,则返回这块空间的地址,
申请失败,则返回 NULL
举个栗子:
//向内存申请一块连续的能放下10个整型的空间
int* ret = (int*)malloc(10 * sizeof(int));
//判断申请是否成功
if (ret == NULL)
{
perror("malloc");
return 1;
}
注意:malloc 的返回值为 void* 类型,因此我们需要对齐强制类型转换!转换为你想要的类型,int* , char* 等等,甚至结构体指针
三、free
该函数包含在头文件 <stdlib.h> 中,它的原型如下:
void free(void* ptr);
功能:释放开辟的动态内存
无返回值
举个例子:
//释放掉刚才开辟的空间
free(ret);
ret = NULL; //防止野指针
在我们用完一块空间后一定要记得释放,不然指向该空间的指针被修改或者销毁,那么你将永远找不到这块空间!而这块空间还在占用你的内存!
四、calloc
该函数包含在 <stdlib.h> 中,它的原型如下:
void* calloc(size_t num, size_t size);
功能:为 num 个大小为 size 的元素开辟一块空间
返回:成功返回空间地址,失败返回 NULL
举个栗子:
//向内存申请一块连续的能放下10个整型的空间
int* p = (int*)calloc(10 ,sizeof(int));
//判断申请是否成功
if (p == NULL)
{
perror("malloc");
return 1;
}
//……使用
//释放
free(p);
p = NULL;
注意:calloc 与 malloc 一样都用于开辟一块动态内存,不同的是,calloc 会给每个空间初始化为 0 ,而 malloc 不会
五、realloc
该函数也是包含在 <stdlib.h> 中,它的原型如下:
void* realloc(void* ptr, size_t size);
功能:调整动态内存的大小
ptr : 被调整的空间地址
size : 调整之后的新大小,单位是字节
返回:成功返回调整之后的新地址,
失败返回 NULL
举个栗子:
//向内存申请一块连续的能放下10个整型的空间
int* p = (int*)calloc(10 ,sizeof(int));
//判断申请是否成功
if (p == NULL)
{
perror("malloc");
return 1;
}
//调整大小为80个字节
int* p2 = (int*)realloc(p, 40 + 40);
//判断是否调整成功
if( p2 == NULL)
{
perror("realloc");
return 1;
}
p = p2;
//释放
free(p);
p = NULL;
这里为什么要用 p2 接收扩容后的空间呢?
因为如果扩容失败,realloc 就会返回 NULL,如果用原来的 p 去接收,那么 p 就被置为 NULL,那么,原来的那块空间就彻底跟你 say 拜拜了
那么,realloc 的细节是什么?真的是扩容吗?
其实不是的,realloc 只是申请一块新的空间,然后把源空间的内容拷贝到新空间,然后在返回新空间的地址
六、常见的动态内存错误
1、对 NULL 的解引用
平时大家肯定不会反这种错误,但有时候 NULL 会伪装自己,蒙蔽了广大学者的慧眼,比如:
int* p = (int*)malloc(INT_MAX);
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
INT_MAX 是有符号int类型的最大值 21247483647 ,很大的一个数字,就会让内存申请失败,返回NULL,而这时候在解引用,就会报错!
为了规避这种错误,建议每申请完一个空间,都要对齐判断是否成功
2、对动态内存开辟空间的越界访问
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i <= 10; i++)
{
p[i] = i;
}
当 i == 10 时,越界访问
这个问题都是很常见的,这里就不多说了
3、对非动态内存开辟的空间使用 free
注意,free 只能释放动态内存空间,也就是用 malloc calloc realloc 开辟出来的空间,至于数组什么的都不可以!
4、用 free 释放动态内存空间的一部分
int* p = (int*)malloc(10 * sizeof(int));
p = p + 2;
free(p);
这样也不可以!free 必须接收该空间的首地址!
5、对同一空间的反复释放
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
free(p);
free(p);
这种也是不可以滴!但是为了避免这种错误,可以在每次释放后,给 p 置为 NULL
七、结语
掌握C语言的动态内存管理,不仅是写出高效程序的关键,也是开发安全稳健软件的重要保障。善用malloc、realloc和free等函数,我们可以灵活应对各种复杂场景,实现资源的动态高效利用。在未来的编程旅程中,让我们不断深入探索与实践,成为内存管理的高手,从而写出更加出色与安全的C语言程序。