【C进阶】分析 C/C++程序的内存开辟与柔性数组(内有干货)
前言:
本文是对于动态内存管理知识后续的补充,以及加深对其的理解。对于动态内存管理涉及的大部分知识在这篇文章中 ---- 【C进阶】 动态内存管理_Dream_Chaser~的博客-CSDN博客
本文涉及的知识内容主要在两方面:
- 简单解析C/C++程序的内存开辟
- 分析柔性数组的知识点
目录
前言:
C/C++程序的内存开辟区域📍
1.栈区(stack)
2. 堆区(heap)
3. 数据段(静态区)(static)
4. 代码段
柔性数组💨
柔性数组的特点
柔性数组的使用
柔性数组的优势
C/C++程序的内存开辟区域📍
1.栈区(stack)
2. 堆区(heap)
3. 数据段(静态区)(static)
4. 代码段

- 实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
- 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
柔性数组💨
柔性数组(Flexible Array)是一种在编程语言中用于表示可变长度的数组的数据结构。它允许在声明数组时不指定数组的长度,而是在运行时根据需要动态分配内存空间。
柔性数组最常见的应用是在C语言中。在C语言中,柔性数组是一种特殊的结构体成员,其长度可以在结构体实例化之前或之后进行动态调整。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
啥意思呢,用代码说话
在vs编译器环境下,以下两种写法均支持
第一种写法(使用空方括号[ ])是更常见和更符合标准的写法,可以在大多数编译器环境下使用。
struct S
{int n;char c;int arr[];//柔性数组成员
};
第二种写法(指定大小为0)在某些特定的编译器(vs)扩展中可能有效,但不具有通用性和可移植性。
struct S
{int n;char c;int arr[0];//柔性数组成员(指定大小)
};
柔性数组的特点
1️⃣结构中的柔性数组成员前面必须至少一个其他成员。
typedef struct st_type
{int i;//必须至少一个其他成员int a[0];//👈柔性数组成员
}type_a;
错误写法:
struct SA
{int arr[];//柔性数组成员
};
2️⃣sizeof 返回的这种结构大小不包括柔性数组的内存
struct S
{int n;char c;int arr[];//柔性数组成员
};
int main()
{printf("%d", sizeof(struct S));
}
🚩8
3️⃣包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{//arr需要开辟的空间是10个int// n与c需要开辟的内存 arr数组需要开辟的内存空间// 8 40struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));return 0;
}
图解:
柔性数组的使用
代码实现🎯
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>//柔性数组
struct S
{int n;char c;int arr[];//柔性数组成员
};int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("%s\n",strerror(errno));return 1;}//使用ps->n = 100;ps->c = 'w';int i = 0;for ( i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d\n", ps->arr[i]);}//调整arr数组的大小(注意这是重新改变大小,不是说在原来空间后面增加,比如说原来是48,那么现在就是88)struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));if (ptr == NULL){printf("%s\n,sterror(error)");return 1;}else{ps = ptr;}//再次使用//....//释放free(ps);ps = NULL;printf("%d\n", sizeof(struct S));return 0;
}
调试一下,看看空间大小如何
malloc的空间,58-30=28(16进制),换成十进制刚好为40,刚好是10int的字节大小
柔性数组的优势
方案一:柔性数组的方案
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>//柔性数组
struct S
{int n;char c;int arr[];//柔性数组成员
};int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("%s\n",strerror(errno));return 1;}//使用ps->n = 100;ps->c = 'w';int i = 0;for ( i = 0; i < 10; i++){ps->arr[i] = i;}for (i = 0; i < 10; i++){printf("%d\n", ps->arr[i]);}//调整arr数组的大小(注意这是重新改变大小,不是说在原来空间后面增加,比如说原来是48,那么现在就是88)struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));if (ptr == NULL){printf("%s\n,sterror(error)");return 1;}else{ps = ptr;}//再次使用//....//释放free(ps);ps = NULL;printf("%d\n", sizeof(struct S));return 0;
}
描述:
malloc 1次 ,free 1次
方案二:结构中指针方案
定义一个指针变量指向一块新的区域,像下面这样
图解:
代码实现✨
struct S
{int n;char c;int* arr;
};int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){perror("malloc");return 1;}int* ptr = (int*)malloc(10 * sizeof(int));if (ptr == NULL){perror("malloc2");return 1;}else{ps->arr = ptr;}//使用ps->n = 100;ps->c = 'w';int i = 0;for (i = 0; i < 10; i++){ps->arr[i] = i;}//打印for (i = 0; i < 10; i++){printf("%d ",ps->arr[i]);}//扩容 - 调整arr的大小ptr = realloc(ps->arr,20*sizeof(int));if (ptr == NULL){perror("realloc");return 1;}else{ps->arr = ptr;}//使用//释放free(ps->arr);ps->arr = NULL;free(ps);ps = NULL;return 0;
}
描述:
malloc 2次,free 2次
上面的方案一和方案二谁的优势更优呢,显然是方案一。
个人的理解:
从写代码的方面来说,malloc越多,free的越多,空间的维护难度就更高,所以
方案一实现起来更加简单,空间维护更加简单,容易维护空间,不易出错
方案二来说,一旦忘记free一次的话,可能会导致内存泄漏等问题,所以维护难度加大,容易出错
还有区别就是:
在堆区上申请内存的话,每一次malloc申请的空间,第二次malloc申请的空间跟第一次申请的空间在地址上不一定是连续的,随机性很高,随着malloc申请的数量越多,那么在内存和内存之间留下的空隙就会越多,这种空隙我们叫做为内存碎片
因为这种内存碎片空间大小比较小一些,那么未来可能被利用到的概率就会比较低一些,所以说,内存碎片越多,那么内存利用率就会越低
总结
①方案一:malloc次数少,内存碎片就会较少,内存的使用率就较高一些
②方案二:malloc次数多,内存碎片就会增多,内存的使用率就下降了
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以, 如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)
本文结束,如有错误,欢迎指正,感谢支持!
相关文章:

【C进阶】分析 C/C++程序的内存开辟与柔性数组(内有干货)
前言: 本文是对于动态内存管理知识后续的补充,以及加深对其的理解。对于动态内存管理涉及的大部分知识在这篇文章中 ---- 【C进阶】 动态内存管理_Dream_Chaser~的博客-CSDN博客 本文涉及的知识内容主要在两方面: 简单解析C/C程序…...

深入理解 JVM 之——字节码指令与执行引擎
更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 类文件结构 Write Once,Run Anywhere 对于 C 语言从程序到运行需要经过编译的过程,只有经历了编译后,我们所编写的代码才能够翻译为机器可以直接运行的二进制代码&#x…...

C++:vector
目录 一、关于vector 二、vector的相关函数 三、相关函数的使用 ①构造函数 ②size ③[] 编辑 ④push_back ⑤迭代器iterator ⑥reserve ⑦resize ⑧find ⑨insert ⑩erase ⑪sort 一、关于vector vector比较像数组 观察可知,vector有两个模板参数…...

Android Automotive编译
系统准备 安装系统 准备一台安装Ubuntu系统的机器(windows系统的机器可以通过WSL安装ubuntu系统) 安装docker 本文使用docker进行编译,因此提前安装docker。参考网络链接安装docker并设置为不使用sudo进行docker操作。 参考链接ÿ…...

什么是50ETF期权开户条件,怎么开期权交易权限?
50ETF期权是指上证50ETF期权,标的物是上证50ETF,代码是(510500),期权是一种在上证50ETF基础上进行衍生品交易的金融工具,下文科普什么是50ETF期权开户条件,怎么开期权交易权限?本文来…...
React 从入门到精通——本文来自AI创作助手
React是一个流行的JavaScript库,用于构建用户界面。以下是React入门到精通的步骤: 入门 安装React 你可以在npm上下载React包,也可以使用其他包管理器。首先需要安装node.js,然后使用以下命令安装React: npm insta…...
【51单片机实验笔记】前篇(三) 模块功能封装汇总(持续更新)
文章目录 通用函数public.hpublic.c 延时函数delay.hdelay.c LED模块数码管模块smg.hsmg.c LED点阵模块独立按键模块矩阵按键模块外部中断模块定时器模块串口通讯模块ADC模块PWM模块 通用函数 包含常用头文件,宏定义,自定义类型,函数工具等。…...

Excel VSTO开发4 -其他事件
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 4 其他事件 针对插件的事件主要有Startup、Shutdown这两个事件,在第2节中已经讲解。在开发窗口中,选择对象…...

语音识别数据的采集方法:基本流程数据类型
“人工智能是一种模仿人类功能的产品。数据采集的方法需要针对特定的场景需求。”—–Mark Brayan (澳鹏CEO) 我们一直说,对于一个高质量的人工智能产品离不开高质量的训练数据。对于不同的人工智能我们需要不同的数据对其训练。要采集正确的数据去训练特定的模型才…...
oracle数据库给用户授权DBA权限Oracle查看哪些用户具有DBA权限
oracle数据库给用户授权DBA权限 步骤一:以sysdba身份登录到Oracle数据库 在授予DBA权限之前,我们首先要以sysdba身份登录到Oracle数据库。使用以下命令登录: sqlplus / as sysdba步骤二:创建用户(如有用户跳过&#…...
024-从零搭建微服务-系统服务(六)
写在最前 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 源码地址(后端):https://gitee.com/csps/mingyue 源码地址(前端):https://gitee.com/csps…...
Arduino驱动TCS3200传感器(颜色传感器篇)
目录 1、传感器特性 2、硬件原理图 3、控制器和传感器连线图 4、驱动程序 TCS3200颜色传感器是一款全彩的颜色检测器,包括了一块TAOS TCS3200RGB感应芯片和4个白色LED灯,TCS3200能在一定的范围内检测和测量几乎所有的可见光。TCS3200有大量的光检测器,每个都有红绿蓝和清…...
基于Matlab实现多个数字水印案例(附上源码+数据集)
数字水印是一种在数字图像或视频中嵌入特定信息的技术,以保护知识产权和防止盗版。在本文中,我们将介绍如何使用Matlab实现数字水印。 文章目录 实现步骤源码数据集下载 实现步骤 首先,我们需要选择一个用于嵌入水印的图像。这可以是原始图像…...

C语言之指针进阶篇(2)
目录 函数指针 函数名和&函数名 函数指针的定义 函数指针的使用 函数指针陷阱 代码1 代码2 注意 函数指针数组定义 函数指针数组的使用 指向函数指针数组的指针 书写 终于军训圆满结束了,首先回顾一下指针进阶篇(1)主要是…...

C++ 进制转化入门知识(1)
一、什么是进制 进制是一种用来表示数值的系统或方法,它是基于一个特定的基数来工作的。在我们常见的几种进制中,有: 1. **二进制(基数 2)**: 二进制只用两个数字:0和1。这是计算机内部使用…...
【React】React学习:从初级到高级(四)
React学习[四] 4 应急方案4.1 使用ref引用值4.1.1 给组件添加ref4.1.2 ref和state的不同之处4.1.3 何时使用ref 4.2 使用ref操作DOM4.2.1 获取指向节点的ref4.2.3 使用 ref 回调管理 ref 列表4.2.4 访问另一个组件的DOM节点4.2.5 用 flushSync 同步更新 state 4.3 使用Effect同…...

微信小程序登录问题(思路简略笔记)
配置问题 这是小程序登录问题,必要的两个配置。 流程思路 1. 微信小程序端,会返回一个code。 2. 查看需要返回给微信小程序端的数据。 3. 既然需要返回三个数据,先看openid如何拿到 WX-Login https://api.weixin.qq.com/sns/jscode2ses…...
Go 锁扩展
文章目录 TryLock统计 goroutine数量读写锁读锁写锁常见死锁情况写锁重入写锁中调用读锁循环依赖 TryLock 源码中自带的(我的go是 1.20版本)TryLock 会尝试获取锁,如果获取不到返回false,并不会进行休眠阻塞(和 Lock的主要区别) func (m *Mutex) TryLo…...

Docker的简介及安装
[shouce]http://shouce.jb51.net/docker_practice/栾一峰菜鸟教程参考文献 1 环境配置的难题 软件开发最大的麻烦事之一,就是环境配置。用户计算机的环境都不相同,你怎么知道自家的软件,能在那些机器跑起来? 用户必须保证两件事…...

安卓核心板的不同核心规格及架构介绍
安卓核心板是将核心功能封装的一块电子主板,集成芯片、存储器和功放器件等,并提供标准接口的芯片。 其特点: ● 能跑 Android 等操作系统 强大的功能及丰富的接口 支持 LCD/TP,Audio,Camera,Video&#…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...