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

目录

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://xiahunao.cn/news/3268848.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【算法】傅里叶变换

一、引言 傅里叶变换是一种在信号处理、图像处理、通信等领域中广泛应用的数学工具。它可以将信号从时域转换到频域&#xff0c;从而揭示信号的频率成分。 二、算法原理 傅里叶变换的基本思想是将一个时域信号分解为多个不同频率的正弦和余弦波的叠加。对于连续信号&#xff0c…

【轻量化神经网络的MCU部署/边缘计算:基于GD32H7】开源GD32AI-ModelZoo工具的完善与详细使用说明

本文档将对gd32ai-modelzoo中的使用方法进行更加细致的介绍。并对原博主提供的gd32ai-modelzoo部分代码进行了修改&#xff0c;使其可以更加顺利地运行。 原开源工程地址&#xff1a;https://github.com/HomiKetalys/gd32ai-modelzoo 原作者博客&#xff1a;https://mbb.eet-ch…

Springboot项目的行为验证码AJ-Captcha(源码解读)

目录 前言1. 复用验证码2. 源码解读2.1 先走DefaultCaptchaServiceImpl类2.2 核心ClickWordCaptchaServiceImpl类 3. 具体使用 前言 对于Java的基本知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09;【Java项目…

vue3前端架构---打包配置

最近看到几篇vue3配置项的文章&#xff0c;转载记录一下 Vue3.2 vue/cli-service 打包 chunk-vendors.js 文件过大导致页面加载缓慢解决方案-CSDN博客文章浏览阅读2k次&#xff0c;点赞8次&#xff0c;收藏9次。Vue3.2 vue/cli-service 打包 chunk-vendors.js 文件过大导致页…

java高级——Exception异常类基本解读

java高级——Exception异常类基本解读 前情提要文章介绍继承结构异常详解1. 异常的定义2. 异常的分类3.3 异常的处理机制3.3.1 try catch finally语句3.3.2 throw关键字3.3.3 throws关键字 4. 浅谈如何有效的避免异常的发生5. 自定义异常6. 常见的RuntimeException 总结 前情提…

我为何撰写有关人工智能和数据科学的文章

撰写人工智能文章的 6 大好处 「AI秘籍」系列课程&#xff1a; 人工智能应用数学基础人工智能Python基础人工智能基础核心知识人工智能BI核心知识人工智能CV核心知识AI 进阶&#xff1a;企业项目实战 可直接在橱窗里购买&#xff0c;或者到文末领取优惠后购买&#xff1a; 自…

C++ //练习 15.30 编写你自己的Basket类,用它计算上一个练习中交易记录的总价格。

C Primer&#xff08;第5版&#xff09; 练习 15.30 练习 15.30 编写你自己的Basket类&#xff0c;用它计算上一个练习中交易记录的总价格。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块&#xff1a; /********************…

数据结构 - 红黑树

文章目录 前言一、红黑树介绍1、红黑树的概念2、红黑树的性质 二、实现红黑树1、基本框架2、插入3、删除4、查找5、测试红黑树6、红黑树代码 三、红黑树性能四、AVL树和红黑树的差别 前言 红黑树是一种二叉搜索树&#xff0c;所以学习前需要学会基本的二叉搜索树&#xff0c;并…

X-AnyLabeling标注软件使用方法

第一步 下载 官方X-AnyLabeling下载地址 github&#xff1a;X-AnyLabeling 第二步 配置环境 使用conda创建新的虚拟环境 conda create -n xanylabel python3.8进入环境 conda activate xanylabel进入X-AnyLabeling文件夹内&#xff0c;运行下面内容 依赖文件系统环境运行环…

昇思MindSpore 应用学习-CycleGAN图像风格迁移互换

日期 心得 昇思MindSpore 应用学习-CycleGAN图像风格迁移互换&#xff08;AI代码学习&#xff09; CycleGAN图像风格迁移互换 模型介绍 模型简介 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;来自论文 Unpaired Image-to-Image Trans…

数据中台 | 3分钟带你读懂数据中台的由来

1.数据中台产生的原因 数据中台的概念起源于中国阿里巴巴集团提出的“大中台&#xff0c;小前台”战略。这一理念的核心在于通过构建强大的中台体系&#xff0c;为前端的快速创新和个性化业务需求提供强有力的支持。具体到数据中台&#xff0c;其设计初衷是为了应对企业内部数…

如何解决Windows系统目录权限问题

目录 前言1. 为什么会出现权限问题2. 修改文件权限的步骤2.1 确定目标文件2.2 右键属性设置2.3 更改所有者2.4 修改权限2.5 确认修改 3. 替换文件3.1 拷贝新的文件3.2 验证替换结果 结语 前言 在Windows系统中&#xff0c;时常需要往C盘系统目录下拷贝或者替换文件。然而&…

Java面试还看传统八股文?快来看看这个场景题合集吧【附PDF】

以下就是这份面试场景文档↓ 这里有什么&#xff1f; ↓↓ 1.针对 2024 年面试行情的变化设计的面试场景题以及回答思路 2. 如何快速通过面试的详细攻略 3. 简历优化技巧 1.知己知彼才能百战百胜&#xff0c;如何做好面试前的准备工作 场景题答案以及更多场景题八股文一线大…

Spring Security学习笔记(二)Spring Security认证和鉴权

前言&#xff1a;本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 上一篇博客介绍了Spring Security的整体架构&#xff0c;本篇博客要讲的是Spring Security的认证和鉴权两个重要的机制。 UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter是…

docker 安装单机版redis

把这三个放上去 修改成自己的 按照自己需求来 照图片做 vim redis.conf vim startRedis.sh mv startRedis.sh deployRedis.sh sh deployRedis.sh docker run --privilegedtrue \ --name dev.redis --restartalways \ --network dev-net \ -v ./config/redis.conf:/etc/r…

编译原理期末复习-按考点

编译原理期末复习-按考点 Ocean University of China 第一章 引论 翻译器、编译器、解释器 翻译器&#xff1a;把一种语言变成另外一种语言&#xff08;语义等价&#xff09; 编译器&#xff1a;翻译器的一种 解释器&#xff1a;不产生目标代码&#xff0c;解释执行源程序&a…

24年第三届钉钉杯大学生大数据挑战赛浅析

需要完整资料&#xff0c;请关注WX&#xff1a;“小何数模”&#xff01; 本次钉钉杯大数据挑战赛的赛题已正式出炉&#xff0c;无论是赛题难度还是认可度&#xff0c;该比赛都是仅次于数模国赛的独一档&#xff0c;可以用于国赛前的练手训练。考虑到大家解题实属不易&#xf…

CentOS 7.x 的 YUM 仓库问题

背景 CentOS Linux 7 的生命周期&#xff08;EOL&#xff09;已经于 2024 年 6 月 30 日终止这意味着 CentOS 7.x 的官方镜像站点将不再提供服务&#xff0c;导致在使用 yum 安装或更新程序时可能会遇到 错误。本文将介绍如何解决这一问题&#xff0c;使得你可以继续在 CentOS…

17 敏捷开发—Scrum(2)

从上一篇 「16 敏捷开发实践&#xff08;1&#xff09;」中了解了Scrum是一个用于开发和维护复杂产品的框架&#xff0c;是一个增量的、迭代的开发过程。一般由多个Sprint&#xff08;迭代冲刺&#xff09;组成&#xff0c;每个Sprint长度一般为2-4周。下面全面介绍Scrumde 角色…

[Windows CMD] 查看网络配置 ipconfig

ipconfig 是一个网络命令工具&#xff0c;用于显示所有适配器&#xff08;网络接口&#xff09;的 IPv4 和 IPv6 配置信息。这个命令在 Windows 操作系统中非常常用&#xff0c;也存在于其他一些基于 IP 的网络系统中&#xff0c;如 macOS 和 Linux&#xff08;在这些系统中通常…