C语言动态内存分配

news/2024/10/11 7:28:37/

        有些情况下需要开辟的空间大小在程序运行过程中才能确定下来,而常规的在栈区开辟空间是在编译时就分配好了内存,并且内存大小不能改变,因此需要引入动态内存分配,动态内存分配的内存是在堆区,需要由用户手动开辟,手动释放,并且可以由用户自行改变空间的大小。下面介绍一下动态内存分配的相关函数:

malloc和free

        malloc是c语言中的动态内存开辟函数,用于在堆区动态开辟一块连续的内存,返回指向这块内存的指针,其函数原型如下:

//size是要开辟的字节数,返回开辟空间的地址,如果开辟失败则返回NULL
//返回值是void*类型,malloc不知道开辟的空间是什么类型,需要用户自己强制类型转换成需要的类型
void* malloc(size_t size);

        free是专门用来做动态内存的释放和回收的函数,函数原型如下:

//ptr指向动态开辟的空间
void free(void* ptr);

        如果malloc的size为0,是标准未定义的,取决于编译器。

        如果free的ptr指向的空间不是动态开辟的,也是标准未定义的。

        如果free的ptr是NULL,则什么都不做。

        malloc和free都声明在stdlib.h头文件中。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{int num = 0;scanf("%d", &num);//键盘输入要开辟的数组大小int *ptr = (int *)malloc(num * sizeof(int));//动态内存开辟int i = 0;for (i = 0; i < num; i++)//为开辟的空间赋值{ptr[i] = i;}for (i = 0; i < num; i++)//打印{printf("%d ", ptr[i]);}free(ptr);//释放开辟的空间,防止内存泄漏ptr = NULL;//将指针置空,防止野指针return 0;
}

        free只是把ptr指向的空间还给了内存,不能再通过ptr访问原来的空间,但是ptr里面存的还是原来的地址,而这个地址里边存放的东西已经不确定了,ptr就变成了野指针,因此内存释放后需要对源地址置为空指针,防止产生野指针。

calloc

        calloc函数也用来动态分配内存,和malloc的区别是calloc会在返回地址前把申请的空间中的每个字节初始化为0,其函数原型如下:

//size是要开辟的元素的大小,num是要开辟的元素个数,如果成功则返回指向开辟空间的指针,失败返回NULL
void* calloc(size_t num, size_t size);
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{int num = 0;scanf("%d", &num);//要开辟元素的个数int *ptr = (int *)calloc(num, sizeof(int));//在堆区开辟num个intint i = 0;for (i = 0; i < num; i++){printf("%d ", ptr[i]);//打印应该是全0,因为calloc会把开辟的元素初始化为0}free(ptr);ptr = NULL;return 0;
}

realloc

        realloc主要用来调整动态开辟的内存的大小,其函数原型如下:

//ptr是要改变大小的空间的地址,size是改变后的大小,如果改变成功则返回改变后的空间地址,失败则返回NULL
void* realloc(void* ptr, size_t size);

        realloc在调整空间大小时有以下两种情况:

①原有空间之后有足够大的空间供扩容:这种情况就直接在原空间后面追加空间,原空间中的数据也不会改变,返回的地址就是原空间的地址。

②原有空间之后没有足够大的空间供扩容:这种情况下会在堆空间上另找一块合适大小的连续空间作为新的空间,会将原空间的数据拷贝到新空间并释放原空间,返回的地址是新空间的地址。

int *ptr = (int*)malloc(100);//开辟100字节的堆空间
if(ptr != NULL)
{
//业务处理
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)如果拓展失败,会把原指针变成NULL,因此需要先用一个新的指针接收扩展后的地址,如果开辟成功,再赋给原变量
//代码2
int*p = NULL;
p = realloc(ptr, 1000);//用新指针接收拓展后的地址
if(p != NULL)
{ptr = p;//返回值不为空再把新指针赋给原指针
}
//业务处理
free(ptr);
ptr =NULL;
return 0;realoc(NULL,40);//等价于malloc(40)

动态内存常见错误

        在开辟和使用动态内存时可能会出现以下一些错误,需要注意并且避免:

①对NULL的解引用操作

int main()
{int* p = (int*)malloc(sizeof(INT_MAX));//动态内存开辟if(NULL == p)//所以在内存开辟后要判断开辟是否成功{return 1}*p = 10;//这里如果内存开辟失败,p就是NULL,解引用NULL会出错free(p);p = NULL;return 0;
}

②对动态开辟空间的越界访问

int main()
{int* p = (int*)calloc(10,sizeof(int));if(NULL == p){return 1;}for(int i = 0; i <= 10; i++)//这里会访问到p[10],然而只开辟了十个int的空间,会越界{p[i] = i;}free(p);p = NULL;return 0;
}

③对非动态开辟的内存用free释放:

int main()
{int a = 10;int* p = &a;free(p);//这里的p指向的是栈区空间,不能用free释放,会崩溃
}

④使用free释放动态开辟空间的一部分:

int main()
{    int* p = (int*)calloc(10,sizeof(int));p++;free(p);//上面改变了p指向的空间,不再指向开辟内存的起始地址,这样释放会崩溃,所以不要篡改申请的堆空间的起始地址
}

⑤对一块动态内存多次释放:

int main()
{int* p = (int*)malloc(sizeof(int));free(p);free(p);//上面已经释放了内存,p已经变成了野指针,再次释放程序会崩溃
}
int main()
{int* p = (int*)malloc(sizeof(int));free(p);p = NULL;//这里就突出了释放空间后将指针置空的重要性free(p);//上面已经释放了内存,但是p被置成了NULL,再次释放什么都不做,不过还是不建议二次释放
}

⑥动态开辟内存忘记释放:

void test()
{int* p = (int*)malloc(4);int flag = 0;scanf("%d",&flag);if(3 == flag)//如果这里输入了3,函数就会直接return,不会执行free,p申请的内存就不会释放,造成内存泄漏return;free(p);p == NULL;return;
}

        如果动态开辟的内存使用完不用free释放的话,这段空间就会内存泄漏,就相当于自己不用也不给其他人用,造成空间浪费。

经典例题

void GetMemory(char* p)
{p = (char*)malloc(100);
}
int main()
{char* str = NULL;GetMemory(str);strcpy(str,"hello world");printf(str);
}
//上述代码的问题如下
//str是实参,p是形参,是传值调用,所以改变p不会改变str,GetMemory结束后p被销毁,
//但是开辟的堆空间没释放,造成内存泄露,而str还是NULL,strcpy对NULL解引用会崩溃。
//应该按以下方法写,改成传地址,并且使用完要释放。
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
int main()
{char* str = NULL;GetMemory(&str);strcpy(str,"hello world");printf(str);free(str);str = NULL
}
char* GetMemory(void)
{char p[] = "hello world";return p;
}
int main()
{char* str = NULL;str = GetMemory();printf(str);
}
//上述代码问题如下
//由于p是局部变量,p中存的是数组首地址,返回p后赋给str,
//但是GetMemory退出后p指向的空间就不在了,str指向的内容已经不是hello world了,变成了野指针。
void test()
{char* str = (char*)malloc(100);strcpy(str,"hello world");free(str);//这里已经释放了str指向的空间if(str != NULL){strcpy(str,"hello");//这里的str已经变成了野指针printf(str);}
}


http://www.ppmy.cn/news/1464046.html

相关文章

SylixOS 版本与 RealEvo-IDE 版本对应关系说明

SylixOS 版本与 RealEvo-IDE 版本对应关系说明 SylixOS 版本IDE 版本发布日期1.4.13.1.52017/01/171.5.23.5.12017/10/121.7.13.8.32018/06/221.8.33.9.52018/10/081.9.9-103.9.102020/01/021.11.63.10.22020/05/131.11.73.10.x2020/06/121.12.93.11.02020/09/111.12.11&#…

学习笔记——动态路由协议——OSPF(OSPF区域)

四、OSPF区域 OSPF路由器在同一个区域(Area)内网络中泛红LSA(链路状态通告)。为了确保每台路由器都拥有对网络拓扑的一致认知&#xff0c;LSDB需要在区域内进行同步。如果OSPF域仅有一个区域&#xff0c;随着网络规模越来越大&#xff0c;LSDB越来越庞大&#xff0c;OSPF路由器…

【C++课程学习】:二叉树的基本函数实现

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 &#x1f349;二叉树的结构类型&#xff1a; &#x1f349;1.创建二叉树函数&#xff08;根据数组&am…

谈谈关于mysql索引的理解

索引 我们在学习java中用来表示数组的下标例如定义一个变量int i 这就表示一个索引,因为索引的英文单词是index,索引也可以称为是书的目录,它可以方便我们查询自己所需要的内容,通过索引我们可以快速找到自己的需求.此时引出了索引的概念,在数据库中. 关于索引的相关操作 有…

【Nginx <三>⭐️⭐️⭐️】Nginx 负载均衡使用

目录 &#x1f44b;前言 &#x1f440;一、 负载均衡概述 &#x1f331;二、项目模拟 2.1 环境准备 2.2 启动多个服务器 2.3 配置 Nginx 2.4 测试配置 &#x1f49e;️三、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;前不久开始学习了 Nginx 的使用&#xff0c;在…

docker image prune -f 命令什么用途

docker image prune -f 命令用于清理系统中未被使用的 Docker 镜像。具体来说&#xff0c;它会删除那些未被任何容器使用的悬空镜像&#xff08;dangling images&#xff09;&#xff0c;从而释放磁盘空间。 以下是 docker image prune -f 命令的具体用途和作用&#xff1a; …

mac安装的VMware虚拟机进行桥接模式配置

1、先进行网络适配器选择&#xff0c;选择桥接模式 2、点击网络适配器 设置... 3、选择WiFi&#xff08;我使用的是WiFi&#xff0c;所以选择这个&#xff09;&#xff0c;注意看右边的信息&#xff1a;IP和子网掩码&#xff0c;后续配置虚拟机的ifcfg-ens文件会用到 4、编辑if…

贪心算法[1]

首先用最最最经典的部分背包问题来引入贪心的思想。 由题意可知我们需要挑选出价值最大的物品放入背包&#xff0c;价值即单位价值。 我们需要计算出每一堆金币中单位价值。金币的属性涉及两个特征&#xff0c;重量和价值。 所以我们使用结构体。 上代码。 #include <i…