当前位置: 首页 > news >正文

【C语言】动态内存开辟

写在前面

C语言中有不少开辟空间的办法,但是在堆上开辟的方法也就只有动态内存开辟,其访问特性与数组相似,但最大区别是数组是开辟在栈上,而动态内存开辟是开辟在堆上的。这篇笔记就让不才娓娓道来。
PS:本篇没有目录实在抱歉CSDN对MK语法支持太差了~🫥😶


一、为什么存在动态内存分配

我们在C语言中已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有几个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
  3. 都是在栈内存中开辟的空间。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 而且我们知道栈内存空间是相对较小的,如果我们需要存放大空间的数据是,一直在栈空间中不释放,很容易造成栈溢出(stack overflow)。堆内存是可以给我们程序猿自己开辟空间的内存,堆内存相比栈是大了非常非常多,一般堆空间和我们硬件的内存相关如(1t,512GB等)。 所以这时候就只能试试动态存开辟了。


二、动态内存函数的介绍

需要调用<stdlib.h>头文件

2.1、 malloc和free

C语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

  • size_t size:代表需要开辟多少字节空间
  • malloc是在堆内存上开辟一个大小为size个字节连续空间
  • malloc开辟的内存空间不会进行初始化操作。

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

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

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

void free (void* ptr);

free函数用来释放动态开辟的内存。

  • 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针,则函数什么事都不做。
  • free释放指针必须指向动态内存的起始地址

malloc与free一般是成对出现的,在程序中,只要不再使用malloc开辟的空间就一定要free,如果不free会造成内存泄漏。如果在大项目中造成内存泄漏,可能会导致灾难。

#include <stdio.h>
int main()
{//代码1int num = 0;scanf("%d", &num);int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int));if (NULL != ptr)//判断ptr指针是否为空{int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0;}}free(ptr);//释放ptr所指向的动态内存ptr = NULL;return 0;
}
  • malloc开辟了 num乘4个字节。
  • 因为是在堆上开辟的连续内存空间,所以可以使用指针+1的方式来范围对应的类型空间
  • 因为ptr是int*类型,所以需要把malloc放回的void*类型强制类型转换成 int*
  • 在free掉动态内存开辟的空间后,prt就变为了野指针,此时需要把prt重新赋值为NULL,以确保不会造成野指针访问。

2.2、calloc

C语言还提供了一个动态内存开辟可以初始化内存的函数叫calloc ,calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

  • 与函数malloc 的区别只在于calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
  • 函数的功能是为num 个大小为size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 虽然calloc可以初始化内存,但是对比起malloc的效率就相对较差,需要灵活运用calloc和malloc来动态开辟内存。
    在这里插入图片描述
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){//使用空间}free(p);p = NULL;return 0;
}

结果为:
在这里插入图片描述


2.3、realloc

在前面的malloccalloc都是固定开辟size个字节内存,如果存满了就需要使用realloc函数来进行扩容。
函数原型:

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

  • ptr 是要调整的malloccalloc的内存地址
  • size 调整之后新大小,size定义多大realloc就会创建多大,一般使用n倍来防止扩容扩小。
  • 在C语言标准中,没有规定realloc不能缩小空间,但是在日常使用中,不推荐使用realloc函数缩容,因为在大工程里面,不是所有人都知道此前的动态内存回缩小,从而导致BUG。
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
    realloc在调整内存空间的是存在两种情况:
    • 情况1:原有空间之后有足够大的空间:要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。(如下图)在这里插入图片描述

    • 情况2:原有空间之后没有足够大的空间:在堆空间上另找一个合适大小的连续空间来使用。若开辟成功:函数返回的是一个新的内存地址。 而且realloc函数会把原动态开辟的地址自动free掉(如下图)。若开辟失败函数将返回空不改变原空间
      在这里插入图片描述在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = malloc(100);if (ptr != NULL){//业务处理}else{perror("malloc::");}//扩展容量int* p = realloc(ptr, 1000);if (p != NULL){printf("%p\n", p);printf("%p\n", ptr);ptr = p;p = NULL;}free(ptr);return 0;
}

结果为:
在这里插入图片描述
上图中我们可以看到,如果扩容空间太大,遇到情况2时候,我们打印原地址和扩容后的地址发现是不相同的。


三、常见的动态内存错误

3.1、对NULL指针的解引用操作、对非动态开辟内存使用free释放

void test()
{int* p = NULL;free(p);//如果p的值是NULL,就会有问题,指针不需要free
}

3.2、对动态开辟空间的越界访问

与数组越界访问相似。

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

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

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}
  • free释放指针必须指向动态内存的起始地址

3.4、对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

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

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

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


四、了解C/C++程序的内存开辟

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

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

为了更直观理解,我们对应下图一起理解:
在这里插入图片描述


五、柔性数组

柔性数组其本质还是使用动态内存开辟空间,只不过是使用数组的形式进行接收罢了。
对于不才来说对于用指针来接收,唯一的优势只有:

  • 如果我们的代码是在一个给别人用的函数中,可以把把结构体的内存以及其成员需要的内存通过柔性数组的特性一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。这样会降低用户忘记释放内存导致内存泄漏。

5.1柔性数组的定义

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

例如:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;

有些编译器会报错无法编译可以改成:

typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;

**柔性数组的特点: **

  • 结构中的柔性数组成员前面必须至少一个其他成员
  • sizeof操作符 返回的这种结构大小 不包括柔性数组的内存
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
    在这里插入图片描述
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;int main() {printf("%d\n", sizeof(type_a));int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));//业务处理p->i = 5;for (i = 0; i < 5; i++){p->a[i] = i;}printf("%d\n", sizeof(type_a));free(p);return 0;
}

结果为:
在这里插入图片描述

  • 上图中我们明显看出,结构体的成员变量i也是存放在柔性数组中的。
  • 无论柔性数组有无动态开辟内存sizeof操作符的计算都不包括柔性数组的内存。

当然,上面的代码可以定义为一个整形指针来接收,也是可以实现上述代码的功能
在这里插入图片描述

typedef struct st_type
{int i;int* p_a;
}type_a;int main() {int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a));assert(p);p->i = 5;p->p_a = (int*)malloc(p->i * sizeof(int));assert(p->p_a);//业务处理for (i = 0; i < 5; i++){p->p_a[i] = i;}//释放空间free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

结果为:
在这里插入图片描述
上述创建指针也是可以完成同样的功能,但是使用柔性数组的实现有两个好处:

  • 第一个好处是:方便内存释放(也是不才觉得唯一的优势…🥶🫡🫡)
    • 如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。
    • 用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员需要free,所以你不能指望用户来发现这个事。
    • 所以,如果我们把结构体的内存以及其成员需要的内存通过柔性数组的特性一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉
  • 第二个好处是:这样有利于访问速度(其实,我个人觉得也没多高了,反正你跑不了
    要用做偏移量的加法来寻址,时间复杂度还是O(n) )
    • 连续的内存有益于提高访问速度,也有益于减少内存碎片。

对于在结构体中的指针还是柔性数组这两个区别,可以阅读一下 陈皓大神写的C语言结构体里的成员数组和指针文章


以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 若有帮助不要吝啬点赞哟~~💖💖

ps:表情包来自网络,侵删🌹

相关文章:

【C语言】动态内存开辟

写在前面 C语言中有不少开辟空间的办法&#xff0c;但是在堆上开辟的方法也就只有动态内存开辟&#xff0c;其访问特性与数组相似&#xff0c;但最大区别是数组是开辟在栈上&#xff0c;而动态内存开辟是开辟在堆上的。这篇笔记就让不才娓娓道来。 PS:本篇没有目录实在抱歉CSD…...

Redis缓存在thinkPHP/fastAdmin框架中的应用

Redis缓存在thinkPHP/fastAdmin框架中的应用 引言 在现代Web开发中&#xff0c;性能优化是一个永恒的话题。随着用户数量的增加和业务逻辑的复杂化&#xff0c;如何提高应用的响应速度和处理能力成为了开发者们必须面对的挑战。缓存技术作为提升性能的有效手段之一&#xff0…...

Ceisum无人机巡检视频投放

公司投标内容有个视频投放的功能动画&#xff0c;原本想实现这么一个效果&#xff1a; 案例效果来自别人的展示作品&#xff0c;Leader一眼就相中了这个效果&#xff0c;可惜别人的终究是别人的&#xff0c;又不会白白给你&#xff0c;终究是要自己动手尝试。 动画方面的展示…...

分享几款开源好用的图片在线编辑,适合做快速应用嵌入

图片生成器是指一种工具或软件&#xff0c;用于自动生成图片或图像内容&#xff0c;通常依据用户设定的参数或模板进行操作。这种工具能够帮助用户快速创建视觉效果丰富的图像&#xff0c;而无需具备专业的设计技能。 在数字化时代&#xff0c;图片编辑已经成为日常工作和生活的…...

闪存学习_1:Flash-Aware Computing from Jihong Kim

闪存学习_1&#xff1a;Flash-Aware Computing from Jihong Kim 前言一、Storage Media&#xff1a;NAND Flash Memory1、概念2、编程和擦除操作3、读操作4、异地更新操作&#xff08;Out-Place Update&#xff09;5、数据可靠性6、闪存控制器&#xff08;SSD主控&#xff09;7…...

如何将本地项目上传至Gitee仓库(详细教程)

前提条件 1、本地电脑安装Git客户端 2、本地已有项目 3、Gitee注册好了账户 如果没有安装Gitee 可以区菜鸟查看一下安装教程 Git教程https://www.runoob.com/git/git-tutorial.html 操作示例 前提条件已经准备好的情况下登录gitee 码云 https://gitee.com 点解右侧加号 新…...

redis详细教程(3.hash和set类型)

hash Redis中的Hash是一种数据结构&#xff0c;用于存储键值对集合。在Redis中&#xff0c;Hash非常适合表示对象&#xff0c;其中对象的每个字段都对应一个键值对。以下是关于Redis中Hash的详细讲解&#xff1a; 特点&#xff1a; 1. 键值对集合&#xff1a;Hash是一个包含…...

性能测试详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、 性能测试术语解释 1. 响应时间 响应时间即从应用系统发出请求开始&#xff0c;到客户端接收到最后一个字节数据为止所消耗的时间。响应时间按软件的特点…...

修改ruoyi的logo和标题

1、修改网页标签上的图标和标题 静态修改 标签页上的 图标 直接替换public/favicon.ico文件为你自己的图标文件 如果你的图标文件名不是favicon.ico&#xff0c;你还需要在public/index.html中相应地修改这一行&#xff0c;将/favicon.ico替换为你的图标文件路径。 静态修改…...

vue系列==vue3新语法

vue系列vue3新语法 1、setup组合式API入口函数 2、利用ref函数定义响应式数据 3、利用reactive函数定义响应式数据 1、setup组合式API入口函数 1、Vue3为组合式API提供了一个setup函数&#xff0c;所有组合式API函数都是在此函数中调用的&#xff0c;它是组合式API的使用入口…...

001-Kotlin界面开发之Jetpack Compose Desktop学习路径

Compose Desktop学习之路 学习过程 理解Kotlin的基本语法 Compose Desktop采用Kotlin构建&#xff0c;因此对Kotlin的基本语法有很好的理解是必不可少的。你可以从官方的Kotlin文档开始。 用一句话概括&#xff0c;Kotlin是一种现代的、静态类型的编程语言&#xff0c;它结合…...

qt QStackedLayout详解

QStackedLayout类提供了一种布局方式&#xff0c;使得在同一时间内只有一个子部件&#xff08;或称为页面&#xff09;是可见的。这些子部件被维护在一个堆栈中&#xff0c;用户可以通过切换来显示不同的子部件&#xff0c;适合用在需要动态显示不同界面的场景&#xff0c;如向…...

python实现钉钉群机器人消息通知(消息卡片)

直接上代码 python """ 飞书群机器人发送通知 """ import time import urllib3 import datetimeurllib3.disable_warnings()class DingTalkRobotAlert():def __init__(self):self.webhook webhook_urlself.headers {Content-Type: applicatio…...

防火墙技术应用

目录 防火墙安全功能指标防火墙性能指标防火墙部署防火墙应用案例-Linux防火墙防火墙应用案例-华为包过滤防火墙ACL(基本)防火墙应用案例-工控防火墙部署 防火墙安全功能指标 我们防火墙也会提供各种各样的互联接口&#xff0c;它这种接口有以太网、快速以太网、千兆以太网&am…...

docker engine stopped

1&#xff09;环境&#xff1a;win 10 2&#xff09;docker安装时已经已经安装了虚拟机 3&#xff09;启用网络适配器 4&#xff09;启用docker服务&#xff08;依赖服务LanmanServer&#xff09; 5&#xff09;全都弄好了&#xff0c;docker还是打不开&#xff0c;没办法了&a…...

Redis- 内核的分配内存限制的警告“WARNING Memory overcommit must be enabled!”

文章目录 问题描述报错原因影响解决方案注意事项 问题描述 [rootredisxxx]# redis-sentinel ./sentinel.conf 19638:X 01 Nov 2024 16:57:27.180 # WARNING Memory overcommit must be enabled! Without it, background save or replication may fail under low memory condi…...

腾讯云在线扩容数据盘

文章目录 一、查询云硬盘使用的分区形式二、根据不同的云硬盘类型进行在线扩容分区2.1 安装 gdisk 工具。2.2 安装 growpart 工具使用 growpart 工具扩容分区 三、根据不同的云硬盘类型进行在线扩容文件系统3.1 扩容 EXT 文件系统3.2 扩容 XFS 文件系统 一、查询云硬盘使用的分…...

Time Travel Queries|在 RisingWave 中访问历史数据

对于许多组织来说&#xff0c;能够访问历史数据十分关键。以金融交易公司为例&#xff1a; 特定时间点的数据快照与合规审查、审计流程息息相关。企业的关键业务系统也依赖历史数据来恢复故障并重建系统。分析过去事件及其对当前状况的影响&#xff0c;还有助于准确预测并制定…...

Unity之UnityWebRequest

复习UnityWebRequest 介绍下载文件和上传文件下载进度和异常验证文件DownloadHandlerScript 介绍 unity中使用UnityWebRequest来代替所有HTTP请求提供了三种API,UnityWebRequest.Get&#xff0c;UnityWebRequest.Post&#xff0c;UnityWebRequest.Put分别处理GET,POST,PUT请求…...

使用 pkg 打包 Puppeteer 应用:跨平台自动化的轻量级选择

使用 pkg 打包 Puppeteer 应用 pkg 是一个非常强大的工具&#xff0c;它允许你将 Node.js 项目打包成独立的可执行文件&#xff0c;这意味着你可以在没有安装 Node.js 环境的设备上运行该应用。这对于希望分发不暴露源代码的应用程序、创建跨平台的快速演示版本或简化部署流程…...

跨境物流监控进入“秒级预警”时代:实测实在Agent风险预警能力深度测评详解

摘要&#xff1a; 步入2026年&#xff0c;全球贸易数字化转型已从“信息化”跨越至“智能体化”阶段。跨境物流监控作为支撑全球供应链的核心枢纽&#xff0c;正面临红海危机常态化、信创国产化替代加速以及数据安全监管趋严的多重挑战。传统的人工监控与初级RPA方案在应对多变…...

Ormar 性能优化:10 个提升数据库查询效率的技巧

Ormar 性能优化&#xff1a;10 个提升数据库查询效率的技巧 【免费下载链接】ormar python async orm with fastapi in mind and pydantic validation 项目地址: https://gitcode.com/gh_mirrors/or/ormar Ormar 是一个专为 FastAPI 设计的 Python 异步 ORM&#xff0c;…...

怎么将5v电升到12v?

开关电源BOOST升压原理首先&#xff0c;12v降到5v&#xff0c;我们可以通过串联一个电阻或者电感&#xff0c;利用串联分压定理&#xff0c;就能实现。那如何把5v升到12v呢&#xff1f;交流电我们可以通过变压器实现。那直流电呢&#xff1f;&#xff08;开关电源BOOST升压原理…...

麒麟KylinOS 2303系统管理员必备:用模板为新用户批量配置统一电源策略

麒麟KylinOS 2303系统管理员实战&#xff1a;批量配置用户电源策略的模板化方案 在企业办公环境或学校机房中&#xff0c;麒麟KylinOS系统管理员经常面临统一管理多台电脑电源策略的需求。传统逐台配置的方式效率低下&#xff0c;而通过/etc/skel/用户模板目录的机制&#xff0…...

手把手教你用Wireshark(或类似工具)理解AMBA AXI总线上的数据流(以Cortex-A53为例)

实战解析&#xff1a;用Wireshark透视Cortex-A53的AXI总线数据流 在嵌入式系统开发中&#xff0c;AXI总线如同SoC的神经系统&#xff0c;承载着处理器核心与各功能模块间的关键通信。对于底层驱动工程师和FPGA开发者而言&#xff0c;能够直观观察总线上的数据流动&#xff0c;就…...

从DJI N3到PX4:高飞老师组px4ctrl状态机实战解析与避坑指南

从DJI N3到PX4&#xff1a;状态机设计与控制逻辑迁移实战指南 在无人机飞控系统开发领域&#xff0c;状态机设计一直是核心难点之一。当开发者需要从DJI N3平台迁移到PX4生态时&#xff0c;控制逻辑的差异往往成为最大的技术障碍。本文将深入解析两种平台的状态机实现差异&…...

Ubuntu虚拟机磁盘空间耗尽导致MySQL启动失败的系统恢复与预防指南

1. 问题现象与核心原因剖析最近在折腾Ubuntu虚拟机时&#xff0c;遇到了一个挺典型的开机故障&#xff1a;系统启动时卡住&#xff0c;屏幕上赫然显示着“Failed to start MySQL Community Server”的错误信息&#xff0c;紧接着系统就停滞不前&#xff0c;无法进入图形界面。这…...

AI赋能泳装设计,今夏爆款如何诞生?

AI赋能泳装设计&#xff0c;今夏爆款如何诞生&#xff1f;随着气温攀升&#xff0c;泳装市场迎来销售旺季。北京先智先行科技有限公司凭借"先知大模型"、“先行AI商学院”、"先知AIGC超级工场"三大旗舰产品&#xff0c;正为泳装行业注入全新活力。传统泳装…...

同样是芯片,为什么有的板子CPU强、有的GPU猛、还有的专门带NPU?三者到底怎么分工?日常选型怎么避坑?

做嵌入式开发、玩工控板、折腾端侧AI的朋友&#xff0c;大概率都纠结过一个问题&#xff1a;同样是芯片&#xff0c;为什么有的板子CPU强、有的GPU猛、还有的专门带NPU&#xff1f;三者到底怎么分工&#xff1f;日常选型怎么避坑&#xff1f;一、通俗拆解&#xff1a;CPU / GPU…...

不止于安装:用Docker在5分钟内快速搭建可复用的ROS Noetic开发环境

5分钟构建可移植ROS开发环境&#xff1a;Docker容器化实战指南 在机器人开发领域&#xff0c;环境配置一直是令人头疼的问题。不同项目依赖的ROS版本冲突、系统库不兼容、团队协作时环境不一致…这些痛点消耗着开发者宝贵的时间。传统安装方式就像在主机上直接"装修"…...