学C的第三十二天【动态内存管理】

news2024/11/17 4:34:25

=========================================================================

相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

学C的第三十一天【通讯录的实现】_高高的胖子的博客-CSDN博客

 =========================================================================

                     

1 . 为什么存在动态内存分配

学到现在认识的内存开辟方式有两种:

              

  • 创建变量:       

int val = 20;        ——        在栈空间上开辟4个字节

  • 创建数组:       

char arr[10] = {10};        ——        在栈空间上开辟10个字节的连续空间

                     

                     

                     

上述的开辟空间的方式有两个特点:

               

  • 空间开辟大小固定的。
  • 数组声明候,必须指定数组的长度,它所需要的内存在编译时分配

                   

但是对于空间的需求不仅仅是上述的情况

有时候我们需要的空间大小在程序运行的时候才能知道

数组的编译时开辟空间的方式就不能满足了。

这时候就只能试试动态内存开辟了。

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2 . 动态内存函数的介绍

(1). malloc 和 free :

                 

malloc :

该函数是一个动态内存开辟的函数

这个函数可以向内存申请一块连续可用的空间

返回指向这块空间的指针

               

书写格式如下

void* malloc (size_t size); 

                

  • 如果开辟成功,则返回一个指向开辟好空间的指针

                

  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查

                  

  • 返回值的类型void* ,所以malloc函数并不知道开辟空间的类型
  • 具体在使用的时候使用者自己决定

               

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

             

  • malloc声明在 stdlib.h 头文件中。

示例:

                       

free :

malloc函数申请的内存空间程序退出时才会还给操作系统

如果程序不退出,动态申请的内存不会主动释放的。

所以需要 free函数释放动态内存

               

书写格式如下

void free (void* ptr);

                

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

        

  • 如果参数 ptr 是NULL指针,则函数什么事都不做

           

  • free声明在 stdlib.h 头文件中。

示例:

                     


                    

(2). calloc :

         

书写格式如下

void* calloc (size_t num, size_t size);

                

  • 函数的功能以 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0

               

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

               

  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

              

  • calloc 声明在 stdlib.h 头文件中。

示例:

                     


                    

(3). realloc :

         

realloc函数的出现让动态内存管理更加灵活

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,

那为了合理地使用内存,我们一定会对内存的大小做灵活的调整

realloc 函数就可以做到对动态开辟内存大小的调整

                                

书写格式如下

void* realloc (void* ptr, size_t size);

                                  

  • ptr 要调整的内存地址,如果填的是NULL空指针,那会开辟一块新的空间跟malloc函数一样

                  

  • size 调整之后新大小

                  

  • 返回值 调整之后的内存起始位置

                  

  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

                  

  • realloc在调整内存空间的是存在两种情况

情况1 --  原有空间之后足够大的空间:

在这种情况下,要扩展内存直接在原有内存之后直接追加空间

原来空间的数据不发生变化

                           

情况2 --  原有空间之后没有足够大的空间:

在这种情况下,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用

旧的空间中的数据拷贝到新的空间中,再释放旧的空间,最后返回新空间的起始地址

这样函数返回的就是一个新的内存地址

                  

  • realloc 声明在 stdlib.h 头文件中。

示例:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

3 . 常见的动态内存错误

(1). 对NULL指针的解引用操作:

                      

malloccallocrealloc函数 可能开辟空间
    开辟空间就有可能会失败返回 NULL空指针
    这时解引用该空指针就可能会出问题

示例:

                     


                    

(2). 对动态开辟空间的越界访问:

示例:

                     


                    

(3). 对非动态开辟内存使用free函数释放:

示例:

                     


                    

(4). 使用free函数释放一块动态开辟内存的一部分:

                      

使用动态空间过程中

改变指向动态空间的指针

这时要使用free函数释放空间就会出问题

示例:

                     


                    

(5). 对同一块动态内存多次释放:

                      

可以在释放动态空间后

将该空间指针设置为空指针

防止多次释放

示例:

                     


                    

(6). 动态开辟内存忘记释放 -- 内存泄漏 :

                      

只有两种方式可以对动态内存进行释放

free函数 程序运行结束

所以如果 忘记释放  没释放且程序无法结束

就会造成内存泄漏

示例:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

4 . 相关经典笔试题

题一:

                 

进行修改:

               

对应代码:

//1:改前
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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();
	return 0;
}


//1:改后
#include <stdio.h>
#include <stdlib.h>
#include <string.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;
}

                     


                    

题二:

               

进行修改:

               

对应代码:

//2:改前
#include <stdio.h>
#include <stdlib.h>

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	//创建空指针:
	char* str = NULL;
	//调用上面的函数:
	str = GetMemory();
	printf(str);
}

int main()
{
	Test();
	return 0;
}


//2:改后
#include <stdio.h>
#include <stdlib.h>

char* GetMemory(void)
{
	static char p[] = "hello world";
	return p;
}

void Test(void)
{
	//创建空指针:
	char* str = NULL;

	str = GetMemory();
	printf(str);
}

int main()
{
	Test();
	return 0;
}

                     


                    

题三:

             

进行修改:

               

对应代码:

//3:改前:
#include <stdio.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;
}


//3:改后:
#include <stdio.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);
	//释放:
	free(str);
	str = NULL;
}

int main()
{
	Test();
	return 0;
}

                     


                    

题四:

                

进行修改:

               

对应代码:

//4:改前:
#include <stdio.h>
#include <stdlib.h>

void Test(void)
{
	//创建动态空间并接收:
	char* str = (char*)malloc(100);

	//使用动态空间:
	strcpy(str, "hello");

	//释放:
	free(str);

	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}


//4:改后:
#include <stdio.h>
#include <stdlib.h>

void Test(void)
{
	//创建动态空间并接收:
	char* str = (char*)malloc(100);

	//使用动态空间:
	strcpy(str, "hello");

	//释放:
	free(str);
	str = NULL;

	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();
	return 0;
}

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

5 . C/C++程序的内存开辟

(1). C/C++程序内存区域划分:

                

C/C++程序内存分配的几个区域:

1. 栈区(stack):

执行函数时函数内局部变量的存储单元都可以在栈上创建

函数执行结束时这些存储单元自动被释放

栈内存分配运算内置于处理器的指令集中效率很高,但是分配的内存容量有限

栈区主要存放运行函数而分配的局部变量函数参数返回数据返回地址等。

2. 堆区(heap):

一般由程序员分配释放若程序员不释放

程序结束时可能由OS(操作系统)回收

分配方式类似于链表

3. 数据段(静态区)(static)

存放全局变量静态数据程序结束后由系统释放

4. 代码段:

存放函数体类成员函数和全局函数)的二进制代码

           

图示:

          

 有了这幅图,

我们就可以更好的理解static关键字修饰局部变量的例子了。

实际上普通的局部变量在栈区分配空间的,

栈区的特点在上面创建的变量出了作用域就销毁

但是被static修饰的变量存放在数据段(静态区)

数据段的特点是在上面创建的变量直到程序结束才销毁

所以生命周期变长

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

6 . 柔性数组

             

C99中,结构体中最后一个元素允许是未知大小的数组,这就叫做柔性数组成员

              

实例:

                  

(1). 柔性数组的特点:

                    

  • 结构体中的柔性数组成员前面必须至少有一个其他成员

  • sizeof 返回的这种结构体大小不包括柔性数组的内存

  • 包含柔性数组成员的结构体malloc ()函数进行内存的动态分配
    并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

              

实例:

                     


                    

(2). 柔性数组的使用:

              

实例:

                     


                    

(3). 柔性数组的优势:

方便内存释放

如果我们的代码是在一个给别人用的函数中

在里面做了二次内存分配(使用两次malloc函数可以实现类似柔性数组的效果)

把整个结构体返回给用户

用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free

所以你不能指望用户来发现这个事

所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了

返回给用户一个结构体指针

用户做一次free就可以把所有的内存也给释放掉

             

有利于访问速度

连续的内存益于提高访问速度

也有益于减少内存碎片(两个开辟的空间中间空余的内存)

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

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

相关文章

ardupilot 中坐标变换矩阵和坐标系变换矩阵区别

目录 文章目录 目录摘要1.坐标变换矩阵与坐标系变换矩阵摘要 本节主要记录ardupilot 中坐标变换矩阵和坐标系变换矩阵的区别,这里非常重要,特别是进行姿态误差计算时,如果理解错误,很难搞明白后面算法。 1.坐标变换矩阵与坐标系变换矩阵 坐标变换矩阵的本质含义:是可以把…

webpack基础知识八:说说如何借助webpack来优化前端性能?

一、背景 随着前端的项目逐渐扩大&#xff0c;必然会带来的一个问题就是性能 尤其在大型复杂的项目中&#xff0c;前端业务可能因为一个小小的数据依赖&#xff0c;导致整个页面卡顿甚至奔溃 一般项目在完成后&#xff0c;会通过webpack进行打包&#xff0c;利用webpack对前…

Django实现音乐网站 ⑸

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是配置媒体资源设置。 目录 配置介绍 设置媒体资源 创建媒体资源目录 修改settings.py 注册媒体资源路由 总结 配置介绍 静态资源是指项目配置的js/css/image等系统常用文件。对于一些经常变动的资源&#x…

华为云交付

文章目录 一、华为云-公有云架构华为公有云的主要服务1.华为云服务—计算类2.华为云服务——存储类3.华为云服务—网络类4.华为云服务—管理和监督类5.华为云数据库 二、待续 一、华为云-公有云架构 华为公有云的主要服务 ECS&#xff1a;弹性云服务器&#xff08; Elastic Cl…

Node.js爬虫只会Cheerio?来试试Puppeteer!

简介 上篇文章我们学习了如何通过 Cheerio 来爬取静态页面&#xff0c;但是我们没有办法处理动态渲染页面的数据 关于 Cheerio 的学习请查看 都 2023 年了还不会 Node.js 爬虫&#xff1f;快学起来&#xff01; 今天我们学习如何使用 Puppeteer 来轻松地完成我们解决不了的爬虫…

webpack基础知识六:说说webpack的热更新是如何做到的?原理是什么?

一、是什么 HMR全称 Hot Module Replacement&#xff0c;可以理解为模块热替换&#xff0c;指在应用程序运行过程中&#xff0c;替换、添加、删除模块&#xff0c;而无需重新刷新整个应用 例如&#xff0c;我们在应用运行过程中修改了某个模块&#xff0c;通过自动刷新会导致…

(三)Node.js - 模块化

1. Node.js中的模块化 Node.js中根据模块来源不同&#xff0c;将模块分为了3大类&#xff0c;分别是&#xff1a; 内置模块&#xff1a;内置模块由Node.js官方提供的&#xff0c;例如fs、path、http等自定义模块&#xff1a;用户创建的每个.js文件&#xff0c;都是自定义模块…

Godot 4 练习 - 制作粒子

演示项目dodge_the_creeps中&#xff0c;有一个Trail&#xff0c;具体运行效果 想要看看咋实现的&#xff0c;看完也不清晰&#xff0c;感觉是要设置某些关键的属性 ChatGPT说&#xff1a;以下是一些重要的属性&#xff1a; texture&#xff1a;用于渲染粒子的纹理。您可以使用…

windows docker部署

windows docker部署 使用 Docker Desktop for Windows安装Docker问题&#xff1a;Update the WSL kernel by running "wsl --update" or follow instructions at https://docs.microsoft.com/windows/wsl/wsl2-kernel.问题描述&#xff1a;解决办法&#xff0c;升级W…

【小沐学前端】GitBook制作在线电子书、技术文档(gitbook + Markdown + node)

文章目录 1、简介1.1 工具简介1.2 使用费用 2、安装2.1 安装node2.2 安装gitbook 3、测试3.1 编辑文档3.2 编译工程3.3 预览工程 结语 1、简介 官网地址&#xff1a; https://www.gitbook.com/1.1 工具简介 什么是 GitBook&#xff1f; GitBook 是一个现代文档平台&#xff…

微信小程序真机防盗链referer问题处理

公司使用百度云存储一些资源&#xff0c;然后现在要做防盗链&#xff0c;在CDN加入Referer白名单后发现PC是正常的&#xff0c;微信小程序无法正常访问资源了。然后是各种查啊&#xff0c;然后发现是微信小程序不支持Referer的修改&#xff0c;且在小程序开发工具是Referer是固…

WIN大恒工业相机SDK开发

大恒工业相机SDK开发概览 一、开发环境搭建1、C# 环境配置&#xff08;VS2019&#xff09;2、C 环境配置&#xff08;VS2019&#xff09;3、python 环境配置&#xff08;Pycharm&#xff09; 二、相机二次开发流程三、相机相机属性参数配置四、图像采集单帧采集回调采集 注意事…

基于边缘无线协同感知的低功耗物联网LPIOT技术:赋能智慧园区方案以及数字工厂领域

回到2000年左右&#xff0c;物联网的底层技术支撑还是“ZigBee”&#xff0c;虽然当时ZigBee的终端功耗指标其实也并不庞大&#xff0c;但是&#xff0c;“拓扑复杂导致工程实施难度大”、“网络规模小导致的整体效率低下”都成为限制其发展的主要因素。 LPWAN&#xff0c;新一…

自学(黑客)技术,入门到入狱!

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟入…

【数据结构篇】手写双向链表、单向链表(超详细)

文章目录 链表1、基本介绍2、单向链表2.1 带头节点的单向链表测试类&#xff1a;链表实现类&#xff1a; 2.2 不带头节点的单向链表2.3 练习测试类&#xff1a;链表实现类&#xff1a; 3、双向链表测试类&#xff1a;双向链表实现类&#xff1a; 4、单向环形链表**测试类**&…

初级web前端开发工程师的岗位职责描述(合集)

初级web前端开发工程师的岗位职责描述1 职责&#xff1a; 1. 根据功能需求设计编写页面原型; 2. 前后端联调保证功能流畅; 3. 提高页面易用性、美观提出合理建议。 4、与后台工程师配合开发联调并交付产品; 5、持续优化前端页面体验和访问速度&#xff0c;保证页面精美高效…

命令行快捷键Mac Iterm2

原文:Jump forwards, backwards and delete a word in iTerm2 on Mac OS iTerm2并不允许你使用 ⌥← 或 ⌥→ 来跳过单词。 你也不能使用 ⌥backspace 来删除整个单词。 下面是在Mac OS上如何配置iTerm2以便能做到这一点的方法。 退格键 首先&#xff0c;你需要将你的左侧 ⌥…

kubernetes基于helm部署gitlab

kubernetes基于helm部署gitlab 这篇博文介绍如何在 Kubernetes 中使用helm部署 GitLab。 先决条件 已运行的 Kubernetes 集群负载均衡器&#xff0c;为ingress-nginx控制器提供EXTERNAL-IP&#xff0c;本示例使用metallb默认存储类&#xff0c;为gitlab pods提供持久化存储&…

python#django数据库一对一/一对多/多对多

一对一OneToOneField 用户和用户信息 搭建 # 一对一 class TestUser(models.Model): usernamemodels.CharField(max_length32) password models.CharField(max_length32) class TestInfo(models.Model): mick_namemodels.CharField(max_length32) usermode…

蓝桥杯上岸必刷!!! (进制、数位专题)

蓝桥杯上岸必刷&#xff01;&#xff01;&#xff01;(进制、数位专题) 距离蓝桥杯省赛倒数最后1天 ❗️ ❗️ ❗️ 还没背熟模板的伙伴们背起来 &#x1f4aa; &#x1f4aa; &#x1f4aa; 大家好 我是寸铁&#x1f4aa; 真题千千万万遍&#xff0c;蓝桥省一自然现&#…