C语言:动态内存管理

目录

为什么存在动态内存分配

动态内存函数

malloc和free

示例

calloc

示例

realloc 

示例

常见的动态内存错误

对NULL指针的解引用操作

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

对于非动态开辟内存使用free释放

使用free释放一块动态开辟内存的一部分

对同一块内存多次释放

动态开辟内存忘记释放(内存泄漏)

经典样例

Demo——程序崩溃,造成内存泄漏

原因

解决方案

Demo——野指针,随机打印值

原因

Demo——程序崩溃,内存泄漏

原因

解决方案


 

为什么存在动态内存分配

原本的内存开辟方式有:

int val = 20 在栈空间上开辟四个字节
char arr [ 10 ] = { 0 };  在栈空间上开辟 10 个字节的连续空间
但是上述的开辟空间的方式有两个特点:
  1. 空间开辟大小是固定的。
  2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。

动态内存函数

malloc和free

C 语言提供了一个动态内存开辟的函数:
void* malloc ( size_t size );
这个函数向内存申请一块 连续可用 的空间,并返回指向这块空间的指针。
  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 0malloc的行为是标准是未定义的,取决于编译器

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下: 

void free ( void* ptr );
free 函数用来释放动态开辟的内存。
  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr NULL指针,则函数什么事都不做。
  3. mallocfree都声明在 stdlib.h 头文件中。

示例

int arr[10] = {0};          // 默认这种开辟的空间是在栈区的,栈区用于存放临时变量int *p = (int *)malloc(40); // 动态内存开辟,开辟40个字节,返回为void *类型,动态开辟的是存放在 堆区if (p == NULL)              // 开辟失败返回空指针{printf("%s\n", strerror(errno));return -1;}for (int i = 0; i < 10; i++){*(p + i) = i;}for (int i = 0; i < 10; i++){printf("%d", *(p + i)); // 0 1 2 3 4 5 6 7 8 9}free(p);  // 先释放p指向的空间p = NULL; // 后此时p是空指针,再也找不到上面动态开辟的内存空间

calloc

C语言还提供了一个函数叫 calloc calloc 函数也用来动态内存分配。原型如下:

void* calloc ( size_t num , size_t size );
  1. calloc函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

示例

    int *p = (int *)calloc(10, sizeof(int));if (p == NULL){printf("%s\n", strerror(errno));return -1;}for (int i = 0; i < 10; i++){printf("%d\n", *(p + i)); // 10个0}free(p);  // 释放p = NULL; // 置空

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc 

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
  • realloc(NULL, 40) == malloc(40) 两者等价
函数原型如下:
void* realloc ( void* ptr , size_t size );
  1. ptr 是要调整的内存地址
  2. size 调整之后新大小
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 的空间。
  5. realloc在调整内存空间的是存在两种情况:
    1. 情况1原有空间之后有足够大的空间
    2. 情况2:原有空间之后没有足够大的空间
情况 1
当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。这样函数返回的是一个新的内存地址。

示例

    int *p = (int *)malloc(40);if (p == NULL){printf("%s\n", strerror(errno));return -1;}for (int i = 0; i < 10; i++){*(p + i) = i + 1; // 此时p指向的数组的值是 1~10}/**realloc扩容:从原本40字节开辟至80字节之所以不直接用p指针接收,是因为realloc可能扩容失败返回空指针,原本p指针还是好好地指向40个字节的数组,这下没扩容,反到给p赋值为了空指针*/int *ptr = (int *)realloc(p, 80);if (ptr != NULL){p = ptr;}for (int i = 0; i < 10; i++) // 验证{printf("%d ", *(p + i)); // 1~10}

常见的动态内存错误

对NULL指针的解引用操作

void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

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

        int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(EXIT_FAILURE);}for(i=0; i<=10; i++){*(p+i) = i;//当i是10的时候越界访问}free(p);

对于非动态开辟内存使用free释放

        int a = 10;int *p = &a;free(p);//err

使用free释放一块动态开辟内存的一部分

 int *p = (int *)malloc(100);p++;free(p);//p不再指向动态内存的起始位置

对同一块内存多次释放

        int *p = (int *)malloc(100);free(p);free(p);//重复释放

动态开辟内存忘记释放(内存泄漏)

经典样例

Demo——程序崩溃,造成内存泄漏

void GetMemory(char *p)
{p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
原因

      因为:str空指针传入GetMemory函数后,形参相当于声明了一个临时p指针用于接收str指针变量,并开启了100字节的动态内存出函数后,临时变量p指向的空间会被销毁,而动态内存所开辟的内存空间依然还在,str依旧是空指针,这就造成后续 strcpy方法 将 "hello world"拷贝到str会崩溃(方法内部会对str进行解引用,NULL解引用会崩溃)

解决方案
void GetMemory(char **p) // 二级指针接收指针的地址
{/* 解引用得到str指针变量,也就是对str所指向的空间动态开辟了100个字节的内存 */*p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(&str); // 传递str指针变量的地址/* 此时str所指向的地址已经有了100个字节的空间,内部解引用,对str赋值字符串 hello world */strcpy(str, "hello world");printf(str); // hello world 打印方式等价于 printf("%s", str); 这种等价不可用于str[]的方式/* 释放动态内存 */free(str);str = NULL;
}

Demo——野指针,随机打印值

char *GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}
原因

   因为p是临时指针变量,出函数,p销毁,所指向的空间被回收或者别占用,p所指向的地址已经不属于p了,当再去解引用,会有随机值。哦,当前都编译不通过 gcc

Demo——程序崩溃,内存泄漏

        void Test(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}}
原因

    因为:free释放动态开辟的内存后,str所指向的空间已经被释放,但是str的地址还是原来的那个地址,并不是空指针进入if后,对不是自己的空间的地址解引用,str也就是野指针了,属于非法访问,程序崩溃

解决方案
void Test_three(void)
{char *str = (char *)malloc(100);strcpy(str, "hello");free(str);str = NULL;if (str != NULL){strcpy(str, "world");printf(str);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/195499.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有成效的工作

从开始上班起&#xff0c;听到过工作是做不完得。 大概的意思&#xff0c;现在的工作做完了&#xff0c;就会分配新的工作。所以总也做不完。 如果是做不完的&#xff0c;那么是不是在一个岗位上就一直干着呢。既然这个很难成立。那其实工作是可以干得完的。 一个岗位的终结&am…

MyISAM和innoDB两种引擎的对比

innoDB 3.23就有了innoDB引擎&#xff0c;5.5成为了默认引擎&#xff0c;支持外键 是一种事务型引擎&#xff0c;可以保证完整提交和回滚 更新、删除比较多的场景&#xff0c;推荐使用innoDB 不过innoDB对内存要求高&#xff0c;因为索引和数据存到一个表了&#xff1b;写操作…

PyQt(学习笔记)

学习资料来源&#xff1a; PyQt快速入门——b站王铭东老师 PyQt&#xff08;学习笔记&#xff09; Pycharm环境准备运行第一个程序QPushButtonQLabelQLineEdit调整窗口大小、位置、图标布局信号与槽PyQt引入多线程 Pycharm环境准备 新建环境——添加PyQt5模块——验证版本 如果…

手写消息队列(基于RabbitMQ)

一、什么是消息队列&#xff1f; 提到消息队列是否唤醒了你脑海深处的记忆&#xff1f;回看前面的这篇文章&#xff1a;《Java 多线程系列Ⅳ&#xff08;单例模式阻塞式队列定时器线程池&#xff09;》&#xff0c;其中我们在介绍阻塞队列时说过&#xff0c;阻塞队列最大的用途…

K-Means聚类

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 K-means聚类算法实现 技术细节 选取的数据集是sklearn.datasets里面的鸢尾花数据集&#xff0c;方便最后的算法评价。 根据手肘法&#xff08;即根据SSE代价函数&#xff09;得出最合适的k值。 此处思路是先根据E …

ZJU Beamer学习手册(二)

ZJU Beamer学习手册基于 Overleaf 的 ZJU Beamer模板 进行解读&#xff0c;本文则基于该模版进行进一步修改。 参考文献 首先在frame文件夹中增加reference.tex文件&#xff0c;文件内容如下。这段代码对参考文献的引用进行了预处理。 \usepackage[backendbiber]{biblatex} \…

最全的接口自动化测试思路和实战:【推荐】混合测试自动化框架(关键字+数据驱动)

混合测试自动化框架(关键字数据驱动) 关键字驱动或表驱动的测试框架 这个框架需要开发数据表和关键字。这些数据表和关键字独立于执行它们的测试自动化工具&#xff0c;并可以用来“驱动&#xff02;待测应用程序和数据的测试脚本代码&#xff0c;关键字驱动测试看上去与手工测…

【wp】2023第七届HECTF信息安全挑战赛 Web

伪装者 考点&#xff1a;http协议flask的session伪造ssrf读取文件 首先根据题目要求就行伪造HTTP 这里不多说&#xff0c;比较基础 然后下面得到是个登入 页面&#xff0c;我们输入zxk1ing 得到 说什么要白马王子 &#xff0c;一眼session伪造 看到ey开头感觉是jwt 输入看看 得…

DrugMAP: molecular atlas and pharma-information of all drugs学习

DrugMAP&#xff1a;所有药物的分子图谱和制药信息 - PMC (nih.gov) DrugMAP: the molecular atlas and pharma-information of drugs (idrblab.net) 构建了一个描述药物分子图谱和药物信息的新数据库&#xff08;DrugMAP&#xff09;。它提供了>30 000种药物/候选药物的相…

深入了解域名与SSL证书的关系

在如今数字化的世界里&#xff0c;网络安全成为我们关注的重要议题之一。为了确保数据在网络上传输的安全性&#xff0c;我们通常会采取各种安全措施&#xff0c;其中最常用的就是SSL证书。然而&#xff0c;很多人并不了解SSL证书是如何与域名相互关联的。 首先&#xff0c;我…

深入理解栈与队列:从基本概念到高级实现

&#x1f493; 博客主页&#xff1a;江池俊的博客⏩ 收录专栏&#xff1a;数据结构探索&#x1f449;专栏推荐&#xff1a;✅cpolar ✅C语言进阶之路&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f525;编译环境&#xff1a;Visual Studio 2022&#x1f389;欢迎大…

李宏毅2023机器学习作业HW05解析和代码分享

ML2023Spring - HW5 相关信息&#xff1a; 课程主页 课程视频 Sample code HW05 视频 HW05 PDF 个人完整代码分享: GitHub | Gitee | GitCode 运行日志记录: wandb P.S. HW05/06 是在 Judgeboi 上提交的&#xff0c;完全遵循 hint 就可以达到预期效果。 因为无法在 Judgeboi 上…