动态内存管理
为什么存在动态内存分配
我们已经掌握的内存开辟方式有 :
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开游10个学节的连续空间
但是上述的开辟空间的方式有两个特点:
1.空间开辟大小是固定的。
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配但是对于空间的需求,不仅仅是上述的情况。
有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态内存开辟了。
动态内存函数介绍
malloc和free
void* malloc(size_t size);
- 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 std1ib.h头文件中。
举个例子:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
int arr[10] = { 0 };//申请的空间在栈区
//动态内存开辟
int* p = (int*)malloc(40);//申请的空间在堆区
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用开辟的内存
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;//释放内存空间
//没有free
//也并不是说内存空间就不回收了
//当程序退出的时候,系统会自动回收内存空间
return 0;
}
calloc
C语言还提供了一个函数叫ca11oc
ca11oc函数也用来动态内存分配。原型如下:
void* ca11oc(size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 ma11oc的区别只在于 ca11oc 会在返回地址之前把申请的空间的每个字节初始化为全0。
举个例子 :
int main()
{
//开辟10个整形空间
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//打印
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
realloc
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。
那rea11oc函数就可以做到对动态开辟内存大小的调整。
函数原型如下 :
void* real1oc(void* ptr, size_t size);
ptr 是要调整的内存首地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况 :
情况1:原有空间之后有足够大的空间直接在原空间后面增加至新的大小,返回原空间首地址
情况2:原有空间之后没有足够大的空间开辟新的一块足够大空间将原来空间的数据拷贝过来释放原来空间返回新开辟空间的首地址
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用开辟的内存
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//扩容
int* ptr = (int*)realloc(p, 80);//80是新的大小
if (ptr != NULL)
{
p = ptr;
}
//使用
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
常见动态内存错误
1对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20; //如果p的值是NULL,就会有问题
//malloc返回的指针要先判断不是空指针再使用
free(p);
p = NULL;
}
注意:对malloc和calloc和realloc返回的指针要判断不为空再使用
2 对动态开辟空间的越界访问
void test()
{
int* p = (int*)mal1oc(10 * sizeof(int));
if (NULL == p)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++) //对动态开辟空间的越界访问是错误的
{
p[i] = i;
}
free(p);
p = NULL;
return 0;
}
注意:即便是动态内存空间,也不能对齐越界访问。
3 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
//...
free(p); // 对非动态开辟内存使用free释放是不行的
p = NULL;
}
注意:free函数只能释放动态内存开辟的空间,对非动态开辟内存使用free释放是不行的。
4 使用free释放一块动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++)
{
*p = i;
p++;
}
//经过for循环里的p++后,p已经没有再指向这块空间的起始位置了
free(p);//所以这里是错误的,不能使用free释放动态空间的一部分
p = NULL;
return 0;
}
注意:不能使用free释放动态内存的一部分
5.对同一块动态内存多次释放
int main()
{
int* p = (int*)malloc(40);
//...
free(p);
//...
free(p); //不能对同一块动态内存多次释放
return 0;
}
注意:不可以对同一块内存空间进行多次释放,每次释放内存空间后,将对应指针置为空指针,可以避免程序因为这种情况报错
6.动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
//...
int flag = 0;
scanf("%d", &flag);
if (flag == 5)
return;//flag == 5,函数返回程序结束p没被释放
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
或者
int* test()
{
//开辟的
int* p = (int*)malloc(100);
if (p == NULL)
{
return p;
}
//...
return p;
}
int main()
{
int* ret = test();
//忘记释放了
return 0;
}
注意:开辟的内存空间及时释放避免造成内存泄漏
相关的经典题目
1.运行Test函数会产生什么结果
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
分析:
void GetMemory(char* p)//把空指针传过来,p一开始是NULL
{
p = (char*)malloc(100);//函数体内部p被赋值为动态内存的首地址
//函数结束回到Test,p被销毁
//动态内存未释放造成内存泄漏
}
void Test(void)
{
char* str = NULL;
GetMemory(str);//传值调用,GetMemory函数没有改变str指针,str指针仍为NULL
strcpy(str, "hello world");//没办法把hello world复制到一个空指针
printf(str);//不会打印
}
int main()
{
Test();
return 0;
}
这段代码想正确实现想要的功能应该进行传址调用,修改如下
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//str存放的就是动态内存开辟的100字节的首地址
strcpy(str, "hello world");
printf(str);
//释放
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
2 运行Test函数会产生什么结果
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
分析:
char* GetMemory(void)
{
//返回栈空间地址的问题
char p[] = "hello world";//局部变量
return p;//返回字符数组p的首地址, 但字符数组这块空间在函数调用结束后将还给操作系统
//这块空间的内容有可能被覆盖
}
void Test(void)
{
char* str = NULL;//创建str指针
str = GetMemory();//调用GetMemory函数,返回的指针为野指针
printf(str);
}
int main()
{
Test();
return 0;
3运行Test函数会产生什么结果
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
分析:
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//动态空间已释放,但忘记把str置为空指针
if (str != NULL)
{
strcpy(str, "world");//到这str是野指针,属于非法访问
printf(str);
}
}
标签:动态内存,管理,int,void,free,C语言,str,NULL
From: https://blog.csdn.net/weixin_46375885/article/details/136763024