动态内存管理那些事:malloccallocreallocfree
Posted 跳动的bit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态内存管理那些事:malloccallocreallocfree相关的知识,希望对你有一定的参考价值。
文章目录
一、为什么存在动态内存分配
🎗 在之前我们都是这样开辟空间的:
int i = 20; //在栈空间开辟4个字节
char arr[10] = { 0 }; //在栈空间开辟10个字节的连续空间
特点
1️⃣ 开辟的空间大小是固定的
2️⃣ 数组在声明的时候,必需包含常量值 (指定数组长度)
小结
以往开辟空间的方式不够灵活,有很大的局限性 (有时候我们需要的空间大小在程序运行的时候才能知道)
所以这篇文章主要了解在内存堆上开辟空间所使用的函数
二、动态内存函数的介绍
💦 malloc
⭕ 函数信息
#include<stdio.h>
#include<stdlib.h>
int main()
{
//假设开辟10个整型的空间:
int arr[10];//1.栈区开辟
int* p = (int*)void* p = malloc(10 * sizeof(int));//2.堆区开辟
/*-----------------分割线-----------------*/
//使用
//1.开辟失败
if(p == NULL)
{
perror("main");
return 0;
}
//2.开辟成功
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;
}
小结
1️⃣ malloc向内存申请一块连续可用的空间,且开辟成功时返回指向那块空间的地址;开辟失败返回NULL
2️⃣ 因为malloc函数的返回值是void*类型,所以在使用某一类型的指针变量接收时,也要强制类型转换为对应的类型
3️⃣ 如果malloc开辟失败,可能会对空指针进行非法解引用操作,所以malloc开辟的空间一定要检查
4️⃣ 使用完malloc开辟的空间,要主动回收空间。因为在回收空间后,那块空间的使用权已经不是自己能控制了,且能通过指针再去寻找到那块空间,所以为了避免非法访问,通常会主动将指向那块空间的指针置为NULL
💦 free
⭕ 函数信息
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
free(p);//1.err,回收栈空间
p = NULL;
free(NULL)//2.等同于-> //free(NULL)
return 0;
}
小结
1️⃣ 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是标准未定义的
2️⃣ 如果参数ptr是NULL指针,则视为无效代码
3️⃣ 关于回收空间有2种方式:一是main函数结束后,开辟的空间会被动的还给OS,但是对于一个每天24小时不停跑的程序来说,如果不主动回收不用的空间的话,剩余的空间将会越来越少。二就是主动的把不用的空间主动回收掉
💦 calloc
⭕ 函数信息
#include<stdio.h>
#include<stdlib.h>
int main()
{
//malloc和calloc都未主动初始化
//1.malloc
int* p = (int*)malloc(40);
if(p == NULL)
return 1;
int i = 0;
for(i = 0; i < 10; i++)
{
printf("%d\\n", *(p + i));
}
free(p);
p = NULL;
//2.calloc
int* q = (int*)calloc(10, sizeof(int));
if(q == NULL)
return 1;
for(i = 0; i < 10; i++)
{
printf("%d\\n", *(q + i));
}
free(q);
q = NULL;
return 0;
}
💨 结果:
小结
1️⃣ calloc相比malloc来说:calloc会主动初始化开辟的内存空间
💦 realloc
🎗 realloc的出现让动态内存管理更加灵活
在申请空间的时候,有时我们会发现过大了或过小了,需要灵活的调整:而能实现灵活调整的函数其实是realloc,所以malloc、calloc、realloc中realloc是毫无争议的一把手
⭕ 函数信息
#include<stdio.h>
#include<stdlib.h>
int main()
{
//1.使用calloc开辟10个整形大小
int* p = (int*)calloc(10, sizeof(int));
if(p == NULL)
{
perror("main");
return 0;
}
//2.使用开辟的空间
int i = 0;
for(i = 0; i < 10; i++)
{
*(p + i) = 5;
}
//3.到了这里还需要10个整型空间,而p所指向的空间已经被使用完了,所以使用realloc调整空间
//为什么这样设计,请看正面详解:
int* pnew = (int*)realloc(p, 20 * sizeof(int));
if(pnew != NULL)
{
p = pnew;
}
//.回收空间
free(p);
p = NULL;
return 0;
}
📝详解:
🎗 realloc开辟原理
❓❔ 思考:如何合适的接收realloc的地址呢
✖ int* p = (int*)realloc(p, 20 * sizeof(int));
如果用旧地址去接收:realloc有可能找不到合适的空间,来调整大小,这时就返回NULL。此时再交给p,不仅空间没开辟好,旧空间的内容也找不到了
—— 偷鸡不成蚀把米
✔ int* pnew = (int*)realloc(p, 20 * sizeof(int));
先用新地址接收,如果开辟成功再把它赋值给旧空间,这样不仅避免了旧空间的丢失,同样也适用场景一、场景二
🎗 realloc单独使用时能实现malloc的效果 (不会初始化)
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)realloc(NULL, 40);//同int* p = (int*)malloc(40);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
💨 结果:
三、常见的动态内存错误
💦 对NULL指针的解引用操作
#include<stdio.h>
#include<stdlib.h>
int main01()
{
int* p = (int*)malloc(10000000000);
int i = 0;
for(i = 0; i < 10; i++)
{
*(p + i) = i;//int* p = NULL; 如果开辟失败,就会非法访问内存
}
return 0;
}
/*--------------------改正--------------------*/
int main()
{
int* p = (int*)malloc(10000000000);
if(p == NULL)
{
perror("main");
return 0;
}
int i = 0;
for(i = 0; i < 10; i++)
{
*(p + i) = i;//int* p = NULL; 如果开辟失败,就会非法访问内存
}
return 0;
}
小结
1️⃣ 对于malloc、calloc、realloc的返回值要作判空理
💦 对动态开辟空间的越界访问
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if(p == NULL)
{
return 0;
}
int i = 0;
for(i = 0; i < 40; i++)
{
*(p + i) = i;//越界访问
}
free(p);
p = NULL;
return 0;
}
💦 使用free释放非动态开辟的空间
#include<stdio.h>
#include<stdlib.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for(i = 0; i < 10; i++)
{
*(p + i) = i;
}
free(p);//这是err的
p = NULL;
return 0;
}
💦 使用free释放一块动态开辟内存的一部分
#include<stdio.h>
#include<stdlib.h>
int main01()
{
int* p = (int*)malloc(10 * sizeof(int));
if(p == NULL)
{
return 0;
}
int i = 0;
for(i = 0; i < 5; i++)
{
*p++ = i;
}
free(p);//没有完全回收
p = NULL;
return 0;
}
/*--------------------改正--------------------*/
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if(p == NULL)
{
return 0;
}
//无非就是想让数组的的前5个元素初始化为0 1 2 3 4,只要不让p真实的往后走即可
int i = 0;
for(i = 0; i < 5; i++)
{
//1.
p[i] = i;
//2.
//*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
小结
1️⃣ 未完全释放动态开辟的空间是err的
2️⃣ 可能会造成内存泄漏,因为没有人再能记住开辟的起始空间了
💦 对同一块动态内存多次释放
#include<stdio.h>
#include<stdlib.h>
int main01()
{
int* p = (int*)malloc(100);
//使用
//...
//释放
free(p);
//...
//...
free(p);//err
return 0;
}
/*--------------------改正--------------------*/
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(100);
//使用
//...
//释放
free(p);
p = NULL;
//...
//...
free(p);//无意义
return 0;
}
小结
1️⃣ 在free完malloc、calloc、realloc开辟空间后,要及时置为NULL
💦 动态开辟内存忘记释放 (内存泄漏)
#include<stdio.h>
#include<stdlib.h>
void test()
{
int* p = (int*)malloc(100);//p是局部变量
if(p == NULL)
return;
//使用
//...
}
int main01()
{
test();
//...
//这里就内存泄漏了:
//在test内动态开辟了空间,忘了释放。且局部变量p也没留下任何遗言,所以在函数外部也释放不了。只要程序没有死,这块空间就没人能找到
return 0;
}
💦 C/C++中程序内存区域划分示意图
1️⃣ 栈区( stack ):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2️⃣ 堆区( heap ) :一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
3️⃣ 数据段(静态区 ) ( static )存放全局变量、静态数据。程序结束后由系统释放。
4️⃣ 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
四、几个经典的笔试题
💦 1.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)//p是str的一份临时拷贝
{
p = (char*)malloc(100);//2.动态开辟空间后没有释放;p为局部变量,出了范围就找不到开辟的空间了,所以内存泄漏
}
void Test(void)
{
char* str = NULL;
GetMemory(str);//值传递
strcpy(str, "hello world");//1.同strcpy(NULL, "hello world")
printf(str);
}
int main01()
{
Test();
return 0;
}
/*--------------------改正1--------------------*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(char* p)
{
p = (char*)malloc(100);
return p;//在局部变量p销毁前,把指向动态开辟好的空间的地址返回回来
}
void Test(void)
{
char* str = NULL;
str = GetMemory(str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main02()
{
Test();
return 0;
}
/*--------------------改正2--------------------*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100); //*p找到str
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//传址
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
💦 2.
#include<stdio.h>
char* GetMemory(void)
{
char p[] = "hello world";//p是局部变量,但是接收的内容是在栈区创建的
return p;//这里虽然返回p的地址,但是这块空间的内容已经销毁了
}
void Test(void)
{
char* str = NULL;
str = 聊聊Block的内存管理那些事