动态内存管理
- 一. malloc 和 free
- 1. malloc
- 2. free
- 二. calloc
- 三. realloc
- 四.动态内存的错误
- 1.对NULL指针的解引用操作
- 2.对动态开辟空间的越界访问
- 3.对非动态开辟内存使用free释放
- 4.使用free释放一块动态开辟内存的一部分
- 5.对同一块动态内存多次释放
- 6.动态开辟内存忘记释放(内存泄漏)
- 五.动态内存经典笔试题分析
前言:
- 当我们要开辟一块连续的内存空间时,我们第一时间想到的可能是数组。但是一但开辟了数组,数组的大小就确定了,无法调整数组的大小。
- 有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
- 于是动态内存开辟函数(malloc,calloc,realloc,free)应运而生,下文带您一一了解其中的奥秘。
一. malloc 和 free
1. malloc
void* malloc(size_t size);
解释:在堆区中开辟一块大小为 size 个字节的空间,返回指向这块空间的起始地址(泛型指针void*)。
因为这块空间存放的数据类型不知(由程序员自己确定),所以用泛型指针接收该地址,在使用的时候记得养成一个好习惯:强制类型转换为自己需要的数据类型。
-
如果开辟成功,则返回一个指向开辟好空间的指针。
-
如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要做检查。
-
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候程序员自己来决定。
-
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
2. free
void free(void* ptr);
解释:free是用来对动态内存的释放和回收的。free 对指针 ptr 指向的内容释放掉,但是指针仍然指向这块空间,若后面不再使用,及时将 ptr 置为 NULL,否则产生野指针。
-
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
-
如果参数 ptr 是NULL指针,则函数什么事都不做。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
//在堆区申请10个整形空间
int* p=(int*)malloc(10*sizeof(int));
if (p == NULL)
{
//开辟空间失败
perror("malloc");//打印错误信息
//printf("%s\n", strerror(errno));//也是打印错误信息
return 1;
}
//使用这块空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//打印这块空间
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放这块空间
free(p);//将这块空间还给了操作系统,我们已经没有权限再使用这块空间了
//但是p仍然指向那块空间
p = NULL;//若不将p置为NULL,那么p就是野指针
return 0;
}
总结:
- 动态内存开辟的函数头文件都是 stdlib.h。
- 如果不释放的话,程序结束的时候也会被操作系统自动释放。
- 但是为了防止内存泄漏,将其置为NULL。这是一个好习惯。
二. calloc
void* calloc(size_t num, size_t size);
解释:在堆区中开辟一块大小为 num * size 个字节的空间,返回指向这块空间的起始地址,其中 num 为数据的个数,size 为单个数据的字节数,同时把申请的空间的每个字节初始化为全为0。
#include<stdio.h>
#include<stdlib.h>
int main()
{
//在堆区申请10个整形空间
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
三. realloc
void* realloc (void* ptr, size_t size);
解释:调整动态内存开辟的空间,ptr 是那块空间的起始地址,size 是调整后的那块空间的字节的个数,返回指向这块空间的起始地址。
#include<stdio.h>
#include<stdlib.h>
int main()
{
//在堆区申请10个整形空间
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//调整空间——变成20个整形空间
int* ptr = (int*)realloc(p, 20 * sizeof(int));//注意:要用新的指针来接收
if (ptr != NULL)
{
p = ptr;
}
else
{
//开辟失败
return 1;
}
int i = 0;
for (i = 0; i < 20; i++)
{
*(p + i) = i + 1;
}
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
注意:也许有些人有疑问为什么要用新的指针接收返回的地址,直接用原来的指针接收不行吗?答案是不行的,在realloc调整动态内存开辟的空间有3中情况,代码如下:
int main()
{
int* p = (int*)malloc(10);
//...
if (p != NULL)
{
int* ptr = (int*)realloc(p, 20);
//...
}
return 0;
}
情况1:
- 开辟的空间后面有足够且连续的空间,只需返回空间的起始地址即可。
情况2:
- 如果后续的空间不够,realloc 函数直接在堆区找一块新的满足大小的空间,将旧的地址,拷贝到新的地址。
- 自动释放旧的地址指向的空间,不需要手动 free,返回新的空间的起始地址。
情况3:
- 堆区已经没有满足情况的连续空间了,返回NULL。
realloc函数也能开辟空间,代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)realloc(NULL, 10 * sizeof(int));//等价于malloc(40)
if (p == NULL)
{
//...
}
return 0;
}
四.动态内存的错误
1.对NULL指针的解引用操作
- 如果将一个空指针(NULL)进行解引用操作,程序会遇到未定义行为,会导致程序崩溃。这是因为空指针并不指向任何有效的内存地址,尝试解引用它会导致访问非法内存,从而导致程序崩溃。
- 因此,在解引用指针之前,应该始终先检查指针是否为空。
错误代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));//可能会开辟失败导致,p等于NULL
//if (p == NULL)
//{
// perror("malloc");
// return 1;
//}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;//如果p等于NULL,对其进行解引用操作,程序会崩溃
}
free(p);
p = NULL;
return 0;
}
2.对动态开辟空间的越界访问
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 40; i++)//越界访问,程序崩溃
{
*(p + i) = i + 1;
}
free(p);
p = NULL;
return 0;
}
3.对非动态开辟内存使用free释放
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
//...
free(p);//程序崩溃
p = NULL;
return 0;
}
4.使用free释放一块动态开辟内存的一部分
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++)
{
*p = i;
p++;//修改了指针p
}
free(p);//free释放一块动态开辟内存的一部分,程序崩溃
p = NULL;
return 0;
}
5.对同一块动态内存多次释放
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
free(p);//p为野指针
//...
free(p);//对同一块动态内存多次释放,程序崩溃
p = NULL;
return 0;
}
6.动态开辟内存忘记释放(内存泄漏)
- 内存泄漏:在程序执行过程中,动态分配的内存空间在程序不再需要时没有被正确释放的情况。这会导致程序在运行过程中持续耗费内存空间而不释放,最终可能导致系统性能下降,甚至导致程序崩溃。
#include<stdio.h>
#include<stdlib.h>
void test()
{
int flag = 1;
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
return;
//使用
if (flag)
return;//未释放,函数提前结束,就找不到那块空间,导致内存泄漏
free(p);
p = NULL;
}
int main()
{
test();
//......
//只有程序结束了,空间才被释放
return 0;
}
- 在一些服务器上(腾讯,阿里…),可能7*7=49天一直在运行,若一直申请内存而不释放,内存迟早有一天会耗尽的,这会造成巨大损失。
- 动态内存管理是一把双刃剑:提供灵活的内存管理方式,但是会带来风险。
- 切记:动态开辟的空间一定要释放,并且正确释放。
五.动态内存经典笔试题分析
例题1:
解决办法:
- 传递 str 的地址通过地址修改 str ,同时可以释放动态内存开辟的空间。
- 返回动态内存开辟的空间的地址,可以释放动态内存开辟的空间。
例题2:
例题3:
解决办法:
创作不易,如果能帮到你的话能赏个三连吗?感谢啦!!!