驾驭代码的无形疆界:动态内存管理揭秘

目录

1.:为什么要有动态内存分配

2.malloc和free

2.1:malloc

 2.2:free

3.calloc和realloc

3.1:calloc

3.1.1:代码1(malloc)

3.1.2:代码2(calloc)

3.2:realloc

3.2.1:原地扩容

3.2.2:异地扩容

3.2.3:代码1(原地扩容)

3.2.3:代码2(异地扩容)

4:常见的动态内存的错误

4.1:对NULL指针的解引用操作

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

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

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

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

4.6:对动态内存忘记释放(内存泄漏)

5:动态内存经典笔试题分析

5.1:题目1(问运行test函数有什么样的结果)

5.1.1:改法1:传二级指针

5.1.2:改法2:以return的形式返回

5.2:题目2(问运行test函数有什么样的结果)

5.3:题目3(问运行test函数有什么样的结果)

5.3.1:修改后

5.4:题目4(问运行test函数有什么样的结果)

5.4.1:修改后

6:C/C++程序的内存开辟区域划分


嘿嘿,uu们, 今天咱们来详细剖析动态内存管理,好啦,废话不多讲,开干!


1.:为什么要有动态内存分配

通过之前的学习,我们已经掌握的内存开辟方式有

//在栈区上开辟四个字节.
int value = 25;
//在栈空间上开辟40个字节的连续空间.
int arr[10] = {0};

但是上面的开辟空间的方式有两个特点:

1.空间开辟大小是固定的.

2.在C99之前,数组在声明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候 才能知道,那么数组的编译时开辟空间的⽅式就不能满足了.
那么C语言引入了动态内存开辟,让我们能够自己开辟和释放空间,这样子的话,就相对来讲比较灵活了.

2.malloc和free

2.1:malloc

C语言提供了一个动态内存开辟的函数, 我们首先来看看官方的解释

void* malloc (size_t size);

malloc这个函数向内存空间申请一块连续可用的空间,并返回指向这块空间的指针.

​​​​​1: 如果开辟成功,则返回⼀个指向开辟好空间的指针.
2: 如果开辟失败,则返回⼀个NULL指针, 因此malloc的返回值⼀定要做检查。
3:返回值的类型是 void*, 所以malloc函数并不知道开辟空间的数据类型,因此具体在使⽤的时候由使用者自己来决定.
4:如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器.   

 2.2:free

当我们自己向内存空间申请了空间后,在使用完后要对其进行释放与回收,那么C语言提供了另外一个函数free,专门用来针对动态内存的释放与回收的.

void free (void* ptr);
free函数⽤来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr NULL指针,那么该函数什么事都不做.
PS:malloc和free在使用时需要包含stdlib.h头文件.
了解了malloc和free函数后,我们来看个例子.
#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请一块空间,用来存放10个整型*/int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){//p[i];*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", p[i]);}/** 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了* 释放ptr所指向的动态内存*/free(p);p = NULL;return 0;
}

3.calloc和realloc

3.1:calloc

C语言还提供了一个函数叫做calloc,calloc函数也可以用来进行动态内存分配,我们来看看其原型

 void* calloc (size_t num, size_t size);

第一个参数是:元素的个数.

第二个参数是:元素占据内存空间的大小. 

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

了解了calloc函数后,我们来看个例子

3.1.1:代码1(malloc)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请一块空间,用来存放10个整型*/int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){//p[i];*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", p[i]);}/** 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了* 释放ptr所指向的动态内存*/free(p);p = NULL;return 0;
}

3.1.2:代码2(calloc)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{//申请一块空间,用来存放10个整型*/int* p = (int*)calloc(10,4);if (p == NULL){perror("calloc");return 1;}//使用int i = 0;for (i = 0; i < 10; i++){//p[i];*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", p[i]);}/** 由于p指针还会指向原本的地址,此时如果不赋为空指针的话, 那就是野指针了* 释放ptr所指向的动态内存*/free(p);p = NULL;return 0;
}

3.2:realloc

  • realloc函数的出现让动态内存管理更加灵活
  • 有时候我们会发现过去申请的空间过小或者过大了,那么为了合理的分配内存,我们就会对内存的大小做出灵活的调整.那么realloc函数就可以对动态开辟的内存进行调整.

  • 第一个参数ptr为要调整的起始内存地址.
  • 第二个参数size为调整之后新大小.
  • 返回值为调整之后的内存起始地址.
  • realloc函数在调整原内存空间大小的基础上,还会将原来内存中的数据拷贝到新的空间.
  • realloc在调整内存空间存在两种情况
    • First:原有空间有足够大的空间那么会原地扩容.
    • Second:原有空间之后没有足够的空间,那么会异地扩容.

3.2.1:原地扩容

当是情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

3.2.2:异地扩容

当是情况2的时候,原有空间之后没有⾜够多的空间时,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是⼀个新的内存地址.

综合上述的两种情况,那么realloc函数的使用就要略微注意一些.

3.2.3:代码1(原地扩容)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = calloc(10, sizeof(int));if (NULL == p){perror("molloc");return 1;}printf("\n");/** 原有空间足够则在原有的空间进行扩展* 如果原有空间后面的空间不足够,则realloc函数会在堆区重新开辟一块足够的空间*/int* ptr = (int*)realloc(p, 20 * sizeof(int));if (ptr != NULL){p = ptr;}return 0;
}

3.2.3:代码2(异地扩容)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = calloc(10, sizeof(int));if (NULL == p){perror("molloc");return 1;}printf("\n");/** 原有空间足够则在原有的空间进行扩展* 如果原有空间后面的空间不足够,则realloc函数会在堆区重新开辟一块足够的空间*/int* ptr = (int*)realloc(p, 1000 * sizeof(int));if (ptr != NULL){p = ptr;}return 0;
}

4:常见的动态内存的错误

4.1:对NULL指针的解引用操作

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{//不对返回值作判断,就可能使用NULL指针,对空指针进行解引用int* p = (int*)malloc(INT_MAX);*p = 20;return 0;
}

上面的代码,由于动态开辟的内存过大,那么因此开辟空间是失败的,那么malloc开辟空间失败的话,就会返回NULL指针.那么此时再对其进行解引用操作的话,就会发生对NULL指针的解引用操作.

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

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = calloc(10, sizeof(int));if (NULL == p){perror("calloc");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;return 0;
}

上述代码则发生对动态开辟空间的越界访问,在开辟空间的时候,是只开辟了数量为10个,大小为4字节的空间,但是在赋值和打印的时候,对第11个空间进行了访问,那么则发生了对动态开辟空间的越界访问.

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

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;//对非动态内存开辟进行释放free(p);p = NULL;return 0;
}

上面的代码则发生对非动态开辟的内存使用了free释放,指针变量p指向的是变量a,指向的区域是栈区,而动态开辟的内存是在堆区,因此发生了对非动态开辟的内存使用了free释放.

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

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;//赋值for (i = 0; i < 5; i++){*p = i;p++;}// 0 1 2 3 4 0 0 0 0//此时p指向第六个位置free(p);p = NULL;return 0;
}

上述的代码是发生了对动态开辟的内存只释放了一部分.

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

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = malloc(40);if (NULL == p){return 1;}free(p);free(p);return 0;
}

上述代码则发生了对同一块动态内存多次释放,对于动态开辟的内存只能够释放一次,这就好比,我们日常去住酒店,当我们退了酒店房间后,是不能够再一次退酒店房间的.

4.6:对动态内存忘记释放(内存泄漏)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test()
{int* p = (int*)malloc(40);if (3){return;}free(p);p = NULL;
}int main()
{test();return 0;
}

总结:忘记释放不再使用的动态开辟的空间会造成内存泄漏

切记:动态开辟的空间一定要正确释放.

5:动态内存经典笔试题分析

5.1:题目1(问运行test函数有什么样的结果)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>void GetMemory(char* p)
{//这个形参出了函数之后就会被销毁外加返回类型为void//malloc开辟的空间没有释放,因此发生了内存泄漏p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;GetMemory(str);//发生对空指针的解引用操作,导致了程序崩溃strcpy(str, "hello world");printf(str);
}int main()
{Test();return 0;
}

5.1.1:改法1:传二级指针

//改法1#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>//传址调用,因此使用二级指针
void GetMemory(char** p)
{*p = (char*)malloc(100);
}void Test(void)
{char* str = NULL;//进行传址调用GetMemory(&str);//发生对空指针的解引用操作,导致了程序崩溃strcpy(str, "hello world");//打印的时候,从字符串的首字符地址开始打印printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

5.1.2:改法2:以return的形式返回

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>//改法2,以return返回的形式
char* GetMemory()
{char* p = (char*)malloc(100);return p;
}void Test(void)
{char* str = NULL;str = GetMemory();strcpy(str, "hello world");printf(str);//开辟了动态内存之后要进行释放并且置为NULL,因为此时str还会指向原本的地址,如果不放置为NULL,那就是野指针了free(str);str = NULL;
}int main()
{Test();return 0;
}

5.2:题目2(问运行test函数有什么样的结果)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
char* GetMemory(void)
{//为局部变量,出了函数就被销毁了char p[] = "hello world";//返回的是地址值return p;
}
void Test(void)
{char* str = NULL;//str非法访问了空间,此时str为野指针str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

5.3:题目3(问运行test函数有什么样的结果)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//开辟的动态内存未进行释放,因此会发生内存泄漏
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

5.3.1:修改后

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//修改后
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}void Test()
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);free(str);str = NULL;
}int main()
{Test();return 0;
}

5.4:题目4(问运行test函数有什么样的结果)

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void Test()
{char* str = (char*)malloc(100);strcpy(str, "hello");//释放了开辟动态内存空间后,指针str还是会指向原本的值,因此此时str为野指针free(str);if (str != NULL){//对野指针的非法访问strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

5.4.1:修改后

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//修改
void Test()
{char* str = (char*)malloc(100);strcpy(str, "hello");/** 释放了开辟动态内存空间后, 指针str还是会指向原本的值, 因此此时str为野指针,释放了以后要对其进行置NULL*/free(str);str = NULL;if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

6:C/C++程序的内存开辟区域划分

  • 1.栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运行函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
  • 2.堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  • 3. 数据段(静态区):存放全局变量、静态数据。程序结束后由系统释放。
  • 4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

好啦,uu们,动态内存管理这部分滴详细知识博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力,同时也欢迎大家来指正博主滴错误~

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

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

相关文章

掀桌子了!原来是咱们的大屏设计太酷,吓着前端开发老铁了

掀桌子了&#xff01;原来是咱们的大屏设计太酷&#xff0c;吓着前端开发老铁了 艾斯视觉观点认为&#xff1a;在软件开发的世界里&#xff0c;有时候创意和设计的火花会擦得特别亮&#xff0c;以至于让技术实现的伙伴们感到既兴奋又紧张。这不&#xff0c;我们的设计团队刚刚…

Vue的安装配置

1.安装node js Node.js — 在任何地方运行 JavaScript (nodejs.org) 2.测试nodejs是否安装成功 node -v npm -v3.通过npm 安装 vue npm install -g vue/cli4.测试vue是否安装成功 vue --version5.打开PyCharm&#xff0c;创建项目&#xff1a;flask-web vue create flask…

【H.264】H.264详解(二)—— H264视频码流解析示例源码

文章目录 一、前言二、示例源码【1】目录结构【2】Makefile源码【3】h264parser.c源码【4】编译运行【5】源码下载地址 声明&#xff1a;此篇示例源码非原创&#xff0c;原作者雷霄骅。雷霄骅&#xff0c;中国传媒大学通信与信息系统专业博士生&#xff0c;在此向雷霄骅雷神致敬…

MySQL客户端命令一节将.sql文件导入MySQL

MySql客户端命令 直接输入SQL语句 使用MySQL客户端连接到服务器之后&#xff0c;可以发送SQL语句到服务器执行&#xff0c;并且以&#xff1b;和\g, \G作为结束不同的结束方式显示内容有所不同** TIPS: ;和\g结尾以表格的形式显示结果\G以行的形式显示结果 在连接到服务器之后…

FineBI连接MySQL5.7

一、在FineBI系统管理中&#xff0c;点击【新建数据库连接】 选择MySQL数据库 配置数据库连接&#xff0c;如下&#xff0c;其中数据库名称就是需要连接的目标数据库

5.CSS学习(浮动)

浮动&#xff08;float&#xff09; 是一种传统的网页布局方式&#xff0c;通过浮动&#xff0c;可以使元素脱离文档流的控制&#xff0c;使其横向排列。 其编写在CSS样式中。 float:none(默认值) 元素不浮动。 float:left 设置的元素在其包含…

Web3 职场新手指南:从技能到素养,求职者如何脱颖而出?

随着 2024 年步入下半年&#xff0c;Web3 行业正在经历一系列技术革新。通过改进的跨链交互机制和兼容性&#xff0c;逐步消除市场碎片化的问题。技术的进步为开发者和用户都打开了新的前景。然而&#xff0c;复杂的技术和快速变化的市场环境也让许多新人望而却步。求职者如何找…

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 目录 Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 一、简单介绍 二、共享纹理 1、共享纹理的原理 2、共享纹理涉及到的关键知识点 3、什么可以实现共享 不能实现共享…

LeetCode 2844.生成特殊数字的最少操作(哈希表 + 贪心)

给你一个下标从 0 开始的字符串 num &#xff0c;表示一个非负整数。 在一次操作中&#xff0c;您可以选择 num 的任意一位数字并将其删除。请注意&#xff0c;如果你删除 num 中的所有数字&#xff0c;则 num 变为 0。 返回最少需要多少次操作可以使 num 变成特殊数字。 如…

Web漏洞扫描工具(AWVS、Goby)

一、背景 想针对自己项目或者小公司的Web安全做相关扫描&#xff0c;自己做漏洞进行自查工作&#xff0c;能够减少自身系统的安全风险&#xff0c;提高系统的安全性。但是没有找到一些开源性质的、扫描质量比较高的相关工具&#xff0c;使用安全公司的专业产品价格又承受不起。…

MySQL_JDBC

目录 一、JDBC常用的接口和类 1.1 数据库连接 Connection 1.2 Statement 对象 二、JDBC的使用 总结 【Java 的数据库编程】 JDBC 即 Java Database Connectivity (Java数据库连接)&#xff0c;是一种用于执行 SQL 语句的 Java API。这个 API 由 java.sql.*,javax.sql.* …

kettle从入门到精通 第八十一课 ETL之kettle kettle中的json对象字段写入postgresql中的json字段正确姿势

1、上一节可讲解了如何将json数据写入pg数据库表中的json字段&#xff0c;虽然实现了效果&#xff0c;但若客户继续使用表输出步骤则仍然无法解决问题。 正确的的解决方式是设置数据库连接参数stringtypeunspecified 2、stringtypeunspecified 参数的作用&#xff1a; 当设置…

ERROR: Cannot find command ‘git’- do you have ‘git’ installed and in your PATH?

ERROR: Cannot find command ‘git’- do you have ‘git’ installed and in your PATH? 目录 ERROR: Cannot find command ‘git’- do you have ‘git’ installed and in your PATH? 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/61780…

Java开发新趋势!MyEclipse v2024.1全新首发——支持AI编码协助

在MyEclipse 2024中&#xff0c;通过Copilot集成提供的AI编码协助&#xff0c;让开发者的生产力提高了近10倍&#xff1b;同时支持Java 22&#xff0c;并部署到最新版本的应用服务器(如WildFly和Payara)&#xff1b;拥有更高性能的Spring工具支持更流畅的编码体验&#xff0c;而…

新增ClamAV病毒扫描功能、支持Java和Go运行环境,1Panel开源面板v1.10.12版本发布

2024年7月19日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布了v1.10.12版本。 在这一版本中&#xff0c;1Panel新增了多项实用功能。社区版方面&#xff0c;1Panel新增ClamAV病毒扫描功能、支持Java和Go运行环境&#xff0c;同时1Panel还新增了文件编辑器…

耳机、音响UWB传输数据模组,飞睿智能低延迟、高速率超宽带uwb模块技术音频应用

在数字化浪潮席卷全球的今天&#xff0c;无线通信技术日新月异&#xff0c;其中超宽带&#xff08;Ultra-Wideband&#xff0c;简称UWB&#xff09;技术以其独特的优势&#xff0c;正逐步成为无线传输领域的新星。本文将深入探讨飞睿智能UWB传输数据模组在音频应用中的创新应用…

Xilinx Ultrascale+ FPGA 驱动MIPI DSI屏显示源码工程

作者&#xff1a;Hello&#xff0c;Panda 大家早上好&#xff0c;中午好&#xff0c;下午好&#xff0c;我是熊猫君。 曾记否&#xff0c;之前熊猫家发了一篇博文《分享一下使用Xilinx FPGA驱动MIPI DSI屏的心路历程》&#xff0c;此文发布以后&#xff0c;后台收到了不少朋友…

FPGA与ASIC:深入解析芯片设计的双子星

前言 在半导体世界里&#xff0c;FPGA&#xff08;Field-Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;与ASIC&#xff08;Application-Specific Integrated Circuit&#xff0c;专用集成电路&#xff09;是两种截然不同的芯片设计策略&#xff0c;各自在…

【Linux】虚拟机安装 openEuler 24.03 X86_64

目录 一、概述 1.1 openEuler 覆盖全场景的创新平台 1.2 系统框架 1.3 平台框架 二、安装详细步骤 一、概述 1.1 openEuler 覆盖全场景的创新平台 openEuler 已支持 x86、Arm、SW64、RISC-V、LoongArch 多处理器架构&#xff0c;逐步扩展 PowerPC 等更多芯片架构支持&…

C++编译jsoncpp库

下载https://github.com/hailong0715/jsoncpp/tree/master windows编译工程 jsoncpp-master\makefiles\vs71 1.msvcprtd.lib(MSVCP140D.dll) : error LNK2005 解决办法&#xff1a; (1).工程(Project)->属性(Properties)->配置属性(Configuration Properties)->c/c-…