文章目录
✨✨✨学习的道路很枯燥,希望我们能并肩走下来!
编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。
前言
本篇对动态内存总结,进一步了解动态内存函数
malloc,calloc,realloc.free.如有错误,请在评论区指正,让我们一起交流,共同进步!
本文开始
1.为什么存在动态内存分配
我们已掌握的内存开辟放式:固定方式
int val = 20; //=》申请4个字节空间
char arr[10] = { 0 };// =》申请10个内存空间
特点:
- 空间开辟大小是固定的
- 数组在申明的时候,必须知道数组的长度,它所需要的内存在编译时分配。
对于固定的空间大小,如果我们想要扩大或者缩小空间,固定的空间就满足不了我们的需求。这时候就需要动态开辟。
2.动态内存函数的介绍
2.1 malloc
C语言提供的动态内存开辟的函数malloc
malloc: 函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
void* :返回类型(开辟空间的起始地址返回到void*)
size_t size: 需要开辟的字节内存大小
为什么是void ?*
只知道开辟的总空间大小,不知道开辟空间大小的类型是什么,所以为了更通用使用void* 来维护。
int main()
{
void* p = malloc(40);
return 0;
}
上述是使用malloc开辟40个字节内存空间,但是我们一般不这样使用,我们如果解引用void* 不知道访问几个字节空间,+1,-1也无法知道内存移动几个字节空间,所以我们一般指定类型如下:
int* p = (int*)malloc(40);
int* p1 = (int*)malloc(10*sizeof(10));
内存如果开辟失败返回NULL,所以开辟空间后一定要判断一下返回不为空。
但是当开辟空间过大时,会产生错误如下图:
动态内存空间开辟时在堆上开辟,如果不主动释放,会一直占用空间(程序不结束的时候)。
总结:
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。返回值的类型是 void ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。*
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
2.2 free
free:内存释放函数
void* ptr:想要释放空间的地址
注意:我们释放的是开辟空间的首地址,如果没有保存首地址,放入的是已经移动的地址,会产生释放失败的情况。(保留开辟空间的首地址)
在free()空间后p指向的还是起始开辟空间的地址(不置空P为野指针),此时如果有人,再次访问这块空间会发生非法访问,所以释放完必须将指针置NULL(代表0);
开辟并释放空间代码示范:
#include<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
//void* p = malloc(40);
int* p = (int*)malloc(2147483647);
//INT_MAX == 2147483647
int* ptr = p;
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*ptr = i;
ptr++;
}
//释放
free(p); //
p = NULL; // 置空
return 0;
}
不释放空间,内存会一直被占用。所以我们开辟空间后,一定要记得释放空间
总结:
free函数用来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
2.3 calloc
calloc也可以用来动态内存分配
num: 开辟元素的个数
size:每个元素的大小(字节)
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//释放
free(p);
p = NULL;
return 0;
}
calloc特点:
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
2.4 realloc
realloc:改变ptr指向的空间大小为size(字节);
- realloc函数的出现让动态内存管理更加灵活。
- 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,为了合理的内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整
- ptr 是要调整的内存地址
- size 调整之后新大小
- 返回值为调整之后的内存起始位置。
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
- realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//增加空间
int* ptr = (int*)realloc(p, 80);
if(ptr != NULL)
{
p = ptr;//要维护p,所以赋值
ptr = NULL;//不用ptr让ptr为空
}
free(p);
p = NULL;
//
return 0;
}
调整空间的两种情况:
realloc(NULL,40); == malloc(40);//realloc其中传递空指针时与malloc一样。
3.常见的动态内存错误
3.1 对非动态开辟内存使用free释放
释放内存是对于动态开辟的内存
int main()
{
//局部变量大括号内创建,出大括号销毁
int num = 10;
int* p = #
//不需要释放
free(p);
p = NULL;
return 0;
}
3.2使用free释放一块动态开辟内存的一部分
释放空间一定要记得空间起始位置
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p = i;
p++;
}
//释放
//释放时P已经不再指向动态内存开辟空间的起始地址了,free会失败
//释放空间一定要记得空间起始位置
free(p);
p = NULL;
return 0;
}
3.3对一块动态内存多次释放
不置空,重复释放会出现错误
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
free(p);
//p = NULL;
free(p); //如果上述p置空,此时可释放,相当于释放NULL作用不大
return 0;
}
3.4动态开辟内存忘记释放(内存泄漏)
动态开辟的空间一定要释放,并且正确释放
释放是指:ptr指向的空间
使用get_,memory()函数开辟动态内存空间,但是忘记释放,会造成内存泄漏。
int* get_memory()
{
int* p = (int*)malloc(40);
return p;
}
int main()
{
int* ptr = get_memory();
//使用ptr
//没有释放
return 0;
}
3.5对NULL指针的解引用操作
开辟动态内存空间,不确定是否开辟空间成功,必须if(p !=NULL)判断开辟空间不为空。
int main()
{
int* p = (int*)malloc(40);
*p = 20;
free(p);
return 0;
}
3.6 对动态开辟空间越界访问
越界访问动态内存空间
int main()
{
int* p = (int*)malloc(40);
int i = 0;
for (i = 0; i <= 10; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
4. 几个经典错误
通过几个经典题目再熟悉一下动态开辟出现的错误。
4.1引用空指针错误
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
}
注意错误:①调用完getMemory函数,str指向的为空,解引用空指针错误。
②malloc开辟的空间没有释放,存在内存泄漏。
图解:
改进代码(传地址):
4.2数组空间被非法访问(返回栈空间地址问题)
p存原来地址,但原有空间已经被释放,再次访问属于非法访问空间。
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
}
图示:
4.3野指针
free() 完没有置空,指针为野指针
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
//str是野指针,这里是非法访问
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
}
总结
✨✨✨各位读友,本篇分享到内容是否更好的让你理解了动态内存开辟,如果对你有帮助给个👍赞鼓励一下吧!!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!
