c语言进阶指南(17)——动态内存管理

欢迎来到博主的专栏——c语言进阶指南
博主id已更新:

文章目录

    • 动态内存分配
    • malloc
    • 动态内存的释放
    • free
    • 其他的动态内存管理函数
    • calloc
    • realloc
      • 使用realloc函数调整动态内存空间
      • 使用realloc函数分配动态内存空间

动态内存分配

动态内存分配是内存分配的一种方法,与此相对的是静态内存分配,在前面我们已经知道了在创建如下变量时,系统会为变量划分一个内存区域

int i;//四个字节的空间
char c;//一个字节的空间
int arr[10];//四十个字节的空间
int *pi;//4/8字节的空间

这些变量的存储形式有以下特点:
(1)这些变量的大小由声明的类型决定的。
(2)这些变量的生命周期取决于创造这个变量的区域,关键字,而非程序。
(3)这些变量的大小在运行过程中不会改变。

如果C语言只有这种特点的变量的话,能实现的操作会受到限制。

比如在创建一个班级的目录时,A班今年有38人,我创建的数组是38个元素,明年A班来了两个转学生,那么是不是又要将整个程序修改一遍呢?当然可以设计一个100元素的数组,但是这又会造成内存浪费了,总之静态内存的管理虽然简单,但是在实现一些操作上会带来一些影响

为了让一些操作变得更加灵活,使用动态内存可以让程序员对自己的程序更加掌握,动态内存的大小由程序决定,生命周期也由程序决定,在程序运行的过程中还能对内存区域进行操作。我们先来了解一下动态内存分配的函数

malloc

malloc的函数原型如下:
在这里插入图片描述

(1)size是一个size_t类型的参数,意思是此次需要开辟的空间,单位字节。
(2)malloc函数会开辟size个字节的空间,这些空间是连续的,随机的,开辟成功后会返回这个空间的起始地址
(3)如果开辟失败,返回空指针

malloc(4);

在这里插入图片描述

可以看到这个空间是一个四个字节的空间,这个空间是没有数据类型,或者说这个空间是作为什么类型的数据来使用是由程序员决定的,可以将其当做一个int类型的数据,也可以用作一个char类型,由4个元素组成的数组

malloc函数开辟的空间是随机的,所以这个空间的起始地址也是随机的,一个随机的地址我们是难以直接使用的,应该使用一个指针指向这个空间来方便访问这个空间.

如果想要把这个空间当成int类型的数据来使用,可以用int*类型的指针来指向。

int *p;
p=malloc(4);
*p=0x11223344;

那么这段空间的数据就会变成(小端环境下)

在这里插入图片描述

如果想要当成char类型的数组来转换,可以用char*类型的指针来指向

char* p;
p = malloc(4);
*(p) = 'a';
*(p + 1) = 'b';
*(p + 2) = 'c';
*(p + 3) = '\0';

在这里插入图片描述

前面提到过这个函数的返回值是一个void类型的指针,所以我们可以在这个过程加上类型转换(不加也没有影响)

int *p=(int*)malloc(4);

开辟的空间用作数组时,要注意p是保持指向起始地址的(这对内存的释放很重要),在指针章节中我们曾通过移动指针来实现对数组进行赋值。

int* p = malloc(sizeof(int) * 10);//在内存中申请一个10个int类型的变量的连续空间
for (int i = 0; i < 10; i++)
{*p++=i;//由于指针自加,导致指针的指向发生移动,这个指针p不再指向起始地址
}

但是动态内存的指针一定要尽可能的指向起始地址,所以得换个方法来赋值,这里给上常用的两种方法
(1)再创建一个指针,让这个指针来移动实现赋值
(2)不移动指针,让指针通过加减的算术运算指向需要操作的地址(推荐)
这里写出第二种方法下的赋值操作

int main()
{int* p = malloc(sizeof(int) * 10);//在内存中申请一个10个int类型的变量的连续空间for (int i = 0; i < 10; i++){*(p + i) = i;//p没有被移动,而是通过算术来定位。}
}

动态内存的释放

局部变量的作用域是有一定范围的,当程序运行超过这个范围时,内存会自动释放。这个变量也就不能再使用了。

而动态内存的释放则不一样,前面已经提到了动态内存是更加灵活的内存分配形式,内存释放的节点是由程序员决定的。而动态内存的释放需要用到一个free函数

free

free的函数原型如下:
在这里插入图片描述

(1) free函数的参数是一个viod*的指针,参数需要是指向一个动态开辟的内存空间的起始地址的指针
(2)free没有返回值

free函数的作用是将这个起始地址的动态内存给释放掉,使用free函数要注意以下几点

(1)free函数的指针一定要指向一个动态开辟好的空间,如果这个指针指向的空间不是动态开辟的内存空间,会造成未定义的结果

未定义的结果:由于c标准中没有定义这个行为会造成什么结果,所以这个代码产生的效果由编译器决定(即不同的编译可能会导致不同的结果)

以vs2022为例

int a = 0;
int* pa = &a;
free(pa);

在这里插入图片描述
可以发现在执行这段代码时vs2022会报错

执行一个未定义的行为会导致未知的结果,所以对于每一个未定义的行为都应该不去使用,即使这个行为会造成正向结果,因为这个代码是不具有可移植性的(不同的编译器会有不同的结果)。而且更严重的后果可能是整个项目的崩溃

(2)free函数参数的指针,必须指向一个动态内存开辟的空间的起始地址,如果在程序的过程中指针发生移动,使得这个指针指向的空间是动态开辟的内存空间,但指针不再指向空间的起始地址,这个使用方式也是错误的

int* p = malloc(40);
for (int i = 0; i < 3; i++)
{*p++ = i;
}
free(p);

在这里插入图片描述可以发现,即使执行程序后的p指向的仍是动态内存空间,但不再是空间的起始地址,那么这个程序就是错误的。

尝试运行可以发现编译器报错
在这里插入图片描述

其他的动态内存管理函数

calloc

calloc函数的函数原型如下:
在这里插入图片描述
calloc函数的作用与malloc的作用类似,都是通过参数开辟一定的动态内存空间,但是calloc在函数的参数上与malloc不同。
在这里插入图片描述

其中num是此次开辟的元素个数
size是一个元素占用的大小,单位字节

也就是说如果想要开辟40字节的空间,那么程序应该这么写

malloc(40;
calloc(4,10);

除此之外calloc肯定会有与malloc不同的作用,不然这个函数就没有存在的意义。

calloc函数开辟的动态空间是初始化好的空间,而malloc函数开辟的空间是未初始化的,在vs2022的内存监视窗口中可以看出
在这里插入图片描述
这是malloc函数开辟的空间,其中cd说明这片数据还没有被初始化。
在这里插入图片描述
这是calloc开辟的内存空间,可以发现这些数据都被初始化成了0.

并非所有的编译器都有这么直观的窗口,我们也可以使用这段代码来发现这两个函数的区别。

int main()
{int* ptr1 = malloc(40);int* ptr2 = calloc(4, 10);int i = 0;for (i = 0; i < 10; i++){printf("%x ", *(ptr1+i));}printf("\n");for (i = 0; i < 10; i++){printf("%x ", *(ptr2+i));}free(ptr1);free(ptr2);return 0;
}

在这里插入图片描述
从代码的运行结果可以看出,malloc函数不会对开辟的内存区域的数据进行初始化,而calloc会将其初始化成0.

realloc

前面只提到了动态内存的分配和释放,但是动态内存还有一个关键的作用就是可以在运行的过程中调节这段内存空间的大小。这里介绍动态内存重分配函数realloc。

先来看看realloc函数的函数原型

在这里插入图片描述

memblock是一个void的指针,这个参数是需要重新分配的空间的指针
size是新空间的大小
realloc函数的返回值是一个void
的指针,这个指针是指向新开辟的空间的起始地址的指针
开辟失败则返回NULL。

使用realloc函数调整动态内存空间

如果将一个函数的第一个参数,是指向动态内存的空间的起始地址的指针,那么这个函数的作用,就是将这个指针指向的动态内存的空间进行大小调整

	int* p = malloc(40);int* ptr = realloc(p, 80);

上述代码的作用是将p指向的那段动态内存的空间,从40字节的大小,变为80字节的大小。
也可以用这个函数将内存空间变小

	int* p = malloc(40);int* ptr = realloc(p, 20);

此时p指向的这段动态内存的空间由40字节变为20字节,而且这个多余的20字节的空间会被直接释放掉。

使用realloc函数分配动态内存空间

如果realloc函数的第一个参数是NULL,那么此时这个函数会随机分配一个大小合适的动态内存空间给你。这和malloc的作用是一致的

malloc(40);
realloc(NULL,40);

此时这两个函数的作用没有区别。

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

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

相关文章

Python教程39:使用turtle画今天日期

---------------turtle源码集合--------------- Python教程36&#xff1a;海龟画图turtle写春联 Python源码35&#xff1a;海龟画图turtle画中国结 Python源码31&#xff1a;海龟画图turtle画七道彩虹 Python源码30&#xff1a;海龟画图turtle画紫色的小熊 Python源码29&a…

向量检索-用最简单的语言

看之前首先要懂两个基本条件&#xff1a; 1. 什么是向量 2. 会使用向量的检索 3. 知道至少一种向量的索引 这里我们拿比较的流行的HNSW算法来进行分析&#xff1a; 最直接的做法是根据向量在给定数据集中采用KNN来找到K个最近的向量。但在实际应用中&#xff0c;待检索的数据往…

14.kubernetes部署Dashboard

Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源 (如 Deployment,Job,D…

k8s的存储卷之静态

存储卷----数据卷 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的&#xff0c;delete&#xff0c;k8s用控制创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态也会回复到初始状态 一旦回到初始状态&#xff0c;所有的后天编辑的文件都会消…

Python自动化测试框架:Unittest 断言详解

断言是编程中常用的一种验证方法&#xff0c;也是测试代码中最重要的部分&#xff0c;用于验证某个条件是否为真&#xff0c;验证测试结果与预期结果是否一致。 unittest 提供了方便的断言方法&#xff0c;用于验证测试结果是否符合预期&#xff0c;若验证失败&#xff0c;则会…

leetcode 动态规划(最后一块石头的重量II、目标和、一和零)

1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度&#xff1a;中等 有一堆石头&#xff0c;每块石头的重量都是正整数。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < …

用友移动管理系统 upload任意文件上传漏洞

产品介绍 用友移动系统管理系统是用友公司推出的一款移动办公解决方案&#xff0c;旨在帮助企业实现移动办公、提高管理效率和员工工作灵活性。 漏洞描述 用友移动系统管理系统/mobsm/common/upload等接口存在任意文件上传漏洞&#xff0c;未经授权攻击者通过漏洞上传任意文…

Flutter组件GridView使用介绍

介绍 GridView 是 Flutter 中用于创建网格布局的滚动小部件。它可以创建多列布局&#xff0c;并且每个网格单元可以包含一个小部件。 GridView 提供了几种构造函数来创建不同类型的网格布局&#xff1a; GridView&#xff1a;最通用的构造函数&#xff0c;完全自定义网格布局…

怎么在unity3D工程中导入Newtonsoft.Json

打开 Unity 编辑器。 转到菜单栏的 “Window”&#xff08;窗口&#xff09;选项&#xff0c;然后选择 “Package Manager”&#xff08;包管理器&#xff09; 在搜索框中输入 “Newtonsoft Json” 进行搜索。 注意&#xff1a;要选择Unity Registry 在搜索结果中&#xf…

GC6109——双通道5V低电压步进电机驱动芯片,低噪声、低振动,应用摄像机,机器人等产品中

GC6109是双通道5V低电压步进电机驱动器&#xff0c;具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦和对焦系统&#xff0c;万向节和其他精密、低噪声的STM控制系统。该芯片为每个通道集成了256微步驱动器。带SPl接口&#xff0c;用户可以方便地调整驱动器的参数。内…

SpringCloudAlibaba微服务架构实战派上下册技术交流!

另外我的新书RocketMQ消息中间件实战派上下册&#xff0c;在京东已经上架啦&#xff0c;目前都是5折&#xff0c;非常的实惠。 https://item.jd.com/14337086.html​编辑https://item.jd.com/14337086.html “RocketMQ消息中间件实战派上下册”是我既“Spring Cloud Alibaba微…

Nginx介绍与安装

目录 nginx服务 1、Nginx 介绍 2、为什么选择 nginx 3、IO多路复用 1、I/O multiplexing【多并发】 2、一个请求到来了&#xff0c;nginx使用epoll接收请求的过程是怎样的? 3、异步&#xff0c;非阻塞 4、nginx 的内部技术架构 5、yum安装部署nginx和配置管理 1.获取…

PHP企业物资管理系统源码带文字安装教程

PHP企业物资管理系统源码带文字安装教程 技术架构 主要框架 : PHP7.0 laravel5.4  mysql5.5.36 composer1.3.2(依赖管理) 前端 : jquery bootstrap jstree&#xff08;树形结构&#xff09; echart&#xff08;图表&#xff09; layer&#xff08;弹出层&#xff09; 企…

JavaScript递归技巧的前世今生:深入解析递归及其与堆栈的关系

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》 ​ ​ ✨ 前言 递归作为一种能够用简洁的方式定义复杂对象的编程技巧,在计算机科学中被广泛应用。它借助系…

OpenHarmony南向之LCD显示屏

OpenHarmony南向之LCD显示屏 概述 LCD&#xff08;Liquid Crystal Display&#xff09;驱动&#xff0c;通过对显示器上下电、初始化显示器驱动IC&#xff08;Integrated Circuit&#xff09;内部寄存器等操作&#xff0c;使其可以正常工作。 HDF Display驱动模型 LCD器件驱…

示例说明 Makefile 中的 $(@F),及其用法示例$$dir $@ $< $^ %.c

备忘一个不错的开源编辑器CudaText 下载网址&#xff1a; CudaText - Browse /release at SourceForge.net CudaText 主页&#xff1a; CudaText - Home 1&#xff0c;含义及验证 在 Makefile 中&#xff0c;$(F) 表示当前规则的目标文件名&#xff08;不包括路径部分&…

都2024年了,FP卖家还不知道AB站怎么玩?

自从开始写FP独立站各种运营技巧和黑科技的文章&#xff0c;经常都会有朋友来私V&#xff0c;询问怎么进行AB站跳转。 可能是现在平台对于FP商家的限制越来越多&#xff0c;再加上如今到处都是“内卷”的电商环境&#xff0c;让FP商家生存越来越艰难&#xff0c;今天就着重讲一…

【踩坑】JDK1.8 AudioSystem 无法关闭流的问题

文章目录 一、前言二、开始狼人杀嫌疑人1&#xff1a;嫌疑人2&#xff1a; 三、复盘Jdk8原生bug解决方法和原理解析 一、前言 做了一个基于文字转语言的小接口&#xff0c;想删除本地wav文件来着&#xff0c;结果发现删除不了。 很明显被占用了&#xff0c;还是被Java占用了……

Linux内核源码分析(强烈推荐收藏!)

前言&#xff1a;Linux内核是由林纳斯托瓦兹&#xff08;Linus Torvalds&#xff09;在1991年开始开发的。当时他为了得到一个可以运行UNIX操作系统的个人计算机&#xff0c;开始编写一个操作系统内核&#xff0c;并将其命名为Linux。随后&#xff0c;越来越多的开发者加入到项…

【论文】:ALOHA双手远程操作手臂

对于机器人来说&#xff0c;诸如穿扎带或插入电池等精细操作任务是众所周知的困难&#xff0c;因为它们需要精确度、接触力的仔细协调以及闭环视觉反馈。执行这些任务通常需要高端机器人、精确的传感器或仔细的校准&#xff0c;这些可能既昂贵又难以设置。学习能否使用低成本且…