C语言柔性数组详解:让你的程序更灵活
柔性数组
- 一、前言
- 二、柔性数组的用法
- 三、柔性数组的内存分布
- 四、柔性数组的优势
- 五、总结
一、前言
仔细观察下面的代码,有没有看出哪里不对劲?
struct S
{int i;double d;char c;int arr[];
};
还有另外一种写法:
struct S
{int i;double d;char c;int arr[0];
};
你应该一眼就看到了,结构体的最后一个成员数组的写法是int arr[];或者是int arr[0],这两种写法是等价的,意思是这个数组的大小是不确定的、未知的、可以变化的。
C99允许这种特殊的结构体存在。这样的结构体满足下面两个条件:
- 最后一个成员变量是一个大小可以变化的数组。
- 这个成员数组前面至少有另外一个成员变量。
我们称这个大小可以变化的成员数组为柔性数组。
注意,柔性数组不能是结构体里唯一一个成员,下面的代码是不允许的:
struct S
{int arr[0];
};
这篇文章里,我将重点探讨柔性数组的用法、内存分布以及和优势。
二、柔性数组的用法
我不建议在栈上直接定义有柔性数组的结构体,也就是这么写:
struct S s;
因为柔性数组的大小是可以变化的,我建议在堆上申请空间,采取动态内存管理的方法,这样就能发挥出柔性数组大小可以改变的优势。
假设我们使用malloc()函数来开辟空间,一开始应该malloc出多大的空间呢?要回答这个问题,首先我们要知道sizeof(struct S)是多少。
事实上,sizeof(struct S)计算出来的结果是该结构体不考虑柔性数组的大小。如果我们想要给柔性数组开辟空间,malloc出来的大小应该是sizeof(struct S)加上柔性数组的大小。
假设这个柔性数组在结构体中的声明是int arr[0];,我想给这个数组的大小是40个字节,这样这个数组就能存储10个int,那么一开始malloc的大小就应该是sizeof(struct S)+10*sizeof(int),具体的例子如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}return 0;
}
该结构体中的i,d,c等变量可以正常使用。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->i = 10;ps->d = 3.14;ps->c = 'F';return 0;
}
柔性数组也可以像正常的数组一样访问,比如把1~10放进去。注意此时这个数组的容量是10个int,不能越界访问。使用的例子如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->i = 10;ps->d = 3.14;ps->c = 'F';for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");return 0;
}
我们还可以对柔性数组扩容,如果我们想让这个柔性数组的容量是20个int,整个结构体的新的大小就是sizeof(struct S)+20*sizeof(int),因为sizeof(struct S)是不考虑柔性数组的大小时计算的结构体大小。只需要对ps进行realloc就行了。实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->i = 10;ps->d = 3.14;ps->c = 'F';for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps = tmp;}return 0;
}
扩容后的柔性数组的空间更大了,我们可以把11~20都放进去。实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->i = 10;ps->d = 3.14;ps->c = 'F';for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps = tmp;}for (int i = 10; i < 20; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 20; i++){printf("%d ", ps->arr[i]);}return 0;
}
当然最后别忘了free掉ps,否则会导致内存泄漏。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->i = 10;ps->d = 3.14;ps->c = 'F';for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps = tmp;}for (int i = 10; i < 20; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 20; i++){printf("%d ", ps->arr[i]);}free(ps);ps = NULL;return 0;
}
对于柔性数组的使用,在上面的例子中,可以总结出几个要点:
- malloc出来的大小是sizeof(struct S)加上柔性数组的大小,calloc同理。
- 扩容时realloc出来的新大小也是sizeof(struct S)加上柔性数组的新大小。
- 每次使用malloc和realloc等函数时,需要检查返回值,否则可能导致对NULL指针的解引用(这点是动态内存管理的常识了)。
- 一定要记得柔性数组的容量是多少,不要越界访问了,空间不够记得扩容。
- 记得free,防止内存泄漏。
三、柔性数组的内存分布
柔性数组是结构体的一个成员数组,在前面的例子中,整个结构体都是在堆上malloc出来的。此时,整个结构体都存储在堆上的一块连续的空间里,包括前面几个成员变量i,d,c和柔性数组arr。也就是这样:

只不过数组arr的大小是可以改变的,所以叫“柔性数组”。
有些朋友可能会说了,我不需要柔性数组也能实现类似这样的效果呀!我在结构体里存一个指针,指向一块malloc出来的空间,这块空间也是堆上的,可以动态管理。也就是说,像下面这样定义结构体:
struct S
{int i;double d;char c;int* arr;
};
这样似乎还简单一点,先malloc出一个struct S出来,malloc的大小就是sizeof(struct S),像这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}return 0;
}
然后再malloc出10个int的大小出来,用结构体中的arr指针来管理这块空间,像这样:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->arr = (int*)malloc(10 * sizeof(int));if (ps->arr == NULL){printf("2: malloc()->%s\n", strerror(errno));return 1;}return 0;
}
此时arr就可以当成一个数组来使用了,比如把1~10放进去。同样还是要注意不要越界访问。示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->arr = (int*)malloc(10 * sizeof(int));if (ps->arr == NULL){printf("2: malloc()->%s\n", strerror(errno));return 1;}for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");return 0;
}
你如果觉得空间不够,还可以扩容。比如,你可以把结构体中的arr进行realloc,新的大小能存放20个int。示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->arr = (int*)malloc(10 * sizeof(int));if (ps->arr == NULL){printf("2: malloc()->%s\n", strerror(errno));return 1;}for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps->arr = tmp;}return 0;
}
此时,你就可以把11~20也放进去。实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->arr = (int*)malloc(10 * sizeof(int));if (ps->arr == NULL){printf("2: malloc()->%s\n", strerror(errno));return 1;}for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps->arr = tmp;}for (int i = 10; i < 20; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 20; i++){printf("%d ", ps->arr[i]);}return 0;
}
最后别忘了把arr和ps都free掉,而且顺序不能错了。如果你先free掉了ps,结构体就没了,里面的arr就成为了野指针,内存就泄露了。实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){printf("malloc()->%s\n", strerror(errno));return 1;}ps->arr = (int*)malloc(10 * sizeof(int));if (ps->arr == NULL){printf("2: malloc()->%s\n", strerror(errno));return 1;}for (int i = 0; i < 10; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 10; i++){printf("%d ", ps->arr[i]);}printf("\n");int* tmp = (int*)realloc(ps->arr, 20 * sizeof(int));if (tmp == NULL){printf("realloc()->%s\n", strerror(errno));return 1;}else{ps->arr = tmp;}for (int i = 10; i < 20; i++){ps->arr[i] = i + 1;}for (int i = 0; i < 20; i++){printf("%d ", ps->arr[i]);}free(ps->arr);ps->arr = NULL;free(ps);ps = NULL;return 0;
}
那这种实现的内存分布是怎么样的呢?这个结构体是存储在堆上的,用ps来管理,结构体里的一个指针arr又指向了堆上的另一块空间,如下图:

这种实现方式和柔性数组的方式感觉差不多呀!都是在堆上有个结构体,结构体里有个大小可以变化的数组。那为什么非要搞出来个柔性数组的概念呢?那是因为,柔性数组有它独特的优势。
四、柔性数组的优势
前面我们先用柔性数组实现了一种效果,又不使用柔性数组实现了相似的效果,对比两种实现方式,我们可以做一些总结:
- 使用上:柔性数组malloc了一次,free了一次;不使用柔性数组要malloc两次,free两次。柔性数组的使用更加简单,不容易出错。如果不使用柔性数组,可能会忘记free掉结构体里的arr指针,导致内存泄漏。
- 效率上:柔性数组的存储空间是连续的,访问时效率更高。
所以,虽然有相似的效果,我更推荐使用柔性数组的方式。
五、总结
在这篇博客里,重点需要掌握以下几点:
- 如果结构体里最后一个成员变量是一个数组,并且大小可以变化,这个成员数组就叫做柔性数组。一个结构体里,除了柔性数组外必须至少有一个成员变量。
- 使用sizeof计算含有柔性数组的结构体大小时,只计算除柔性数组之外的空间大小。
- 使用柔性数组,比不使用柔性数组操作更加简单,不易出错,且效率更高。
相关文章:
C语言柔性数组详解:让你的程序更灵活
柔性数组 一、前言二、柔性数组的用法三、柔性数组的内存分布四、柔性数组的优势五、总结 一、前言 仔细观察下面的代码,有没有看出哪里不对劲? struct S {int i;double d;char c;int arr[]; };还有另外一种写法: struct S {int i;double …...
Redis-带你深入学习数据类型list
目录 1、list列表 2、list相关命令 2.1、添加相关命令:rpush、lpush、linsert 2.2、查找相关命令:lrange、lindex、llen 2.3、删除相关命令:lpop、rpop、lrem、ltrim 2.4、修改相关命令:lset 2.5、阻塞相关命令:…...
react拖拽依赖库react-dnd
注:对于表格自定义行可以拖拽和树自定义节点可以拖拽等比较适用,其余的拖拽处理可以使用dragstart,drop等js原生事件来实现 react-dnd使用方法很简单,直接上干货 第一步安装依赖并引入 import { DndProvider } from react-dnd;…...
win10环境安装使用docker-maxwell
目的:maxwell可以监控mysql数据变化,并同步到kafka、mq或tcp等。 maxwell和canal区别: maxwell更轻量,canal把表结构也输出了 docker bootstrap可导出历史数据,canal不能 环境 :win10,mysql5…...
Docker部署RabbitMQ
Docker部署RabbitMQ 介绍 RabbitMQ是一个开源的消息队列系统,它被设计用于在应用程序之间传递消息。它采用了AMQP(高级消息队列协议)作为底层通信协议,这使得它能够在不同的应用程序之间进行可靠的消息传递。 那么,…...
23个react常见问题
1、setState 是异步还是同步? 合成事件中是异步 钩子函数中的是异步 原生事件中是同步 setTimeout中是同步 相关链接:你真的理解setState吗?: 2、聊聊 react16.4 的生命周期 图片 相关连接:React 生命周期 我对 Reac…...
【python基础】——Anaconda下包更新的坑及安装与卸载、及安装后Jupyter Notebook没反应的解决方法
文章目录 前言一、起因:如何一步步走到卸载重装anaconda?二、卸载anaconda二、重新安装anaconda三、关于安装Anaconda后,打开Jupyter Notebook运行代码没反应且in[ ]没有*前言 本文主要用来记录自己近期踩坑的一些复盘。其中坑有: ‘.supxlabel’ 不起作用的解决pip list 与…...
CSS 中的 display 和 visibility
CSS 中的 display 和 visibility 都可以设置一个元素在浏览器中的显示或隐藏效果。 display: 隐藏某个元素时,不会占用任何空间。换句话讲,不会影响布局。visibility: 隐藏某个元素时,仍需占用与未隐藏之前一样的空间。换句话讲,…...
解决mysql报错this is incompatible with DISTINCT
环境 centos 9 php7.4 mysql5.7 问题 mysql查询报如下错误: SQLSTATE[HY000]: General error: 3065 Expression #1 of ORDER BY clause is not in SELECT list, references column hst_csc.q.timestamp which is not in SELECT list; this is incompatible with…...
C++-map和set
本期我们来学习map和set 目录 关联式容器 键值对 pair 树形结构的关联式容器 set multiset map multimap 关联式容器 我们已经接触过 STL 中的部分容器,比如: vector 、 list 、 deque 、forward_list(C11)等,这些容器统称为序列式…...
微信小程序AI类目-深度合成-AI问答/AI绘画 互联网信息服务算法备案审核通过教程
近期小程序审核规则变化后,很多使用人类小徐提供的chatGPT系统的会员上传小程序无法通过审核,一直提示需要增加深度合成-AI问答、深度合成-AI绘画类目,该类目需要提供互联网信息服务算法备案并上传资质,一般对企业来说这种务很难实…...
蓝桥杯官网练习题(星期一)
题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 整个 2020 世纪(1901 年 1 月 1 日至 2000 年 12 月 3131 日之间),一共有多少个星期一?(不要告诉我你不知道今天是星…...
centos7更新podman
实验环境:centos7.7.1908 1.安装podman并查看版本 yum install podman podman -v 当前podman版本信息是1.6.4 2.更新podman版本 通过查看资料显示centos 7 支持最高版本为 3.4.4,更新podman大致有以下四步: golang 安装(本次使用版本: 1.…...
Java特性之设计模式【抽象工厂模式】
一、抽象工厂模式 概述 抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式 在抽象工厂模式中,接口是…...
机器学习简介
引言 为何现在机器学习如此热门? 主要原因是由于“人类无论如何也做不到在短时间内实现从大量的数据中自动的计算出正确的结果操作”。 什么是机器学习? 所谓的机器学习,就是通过对数据进行反复的学习,来找出其中潜藏的规律和模式…...
linux之perf(2)list事件
Linux之perf(2)list事件 Author:Onceday Date:2023年9月3日 漫漫长路,才刚刚开始… 参考文档: Tutorial - Perf Wiki (kernel.org)perf-list(1) - Linux manual page (man7.org) 1. 概述 perf list用于列出可用的性能事件,这…...
将多个EXCEL 合并一个EXCEL多个sheet
合并老版本xls using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using NPOI.HSSF.UserModel; …...
【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例
前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄,vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄ÿ…...
微信小程序——数据绑定
在微信小程序中,可以通过以下代码实现数据绑定: 在WXML中,使用双大括号{{}}绑定数据,将数据渲染到对应的视图中。 <view>{{message}}</view>在JS中,定义一个数据对象,并将其绑定到页面的data…...
libbpf-bootstrap安卓aarch64适配交叉编译
1.为什么移植 疑惑 起初我也认为,像libbpf-bootstrap这样在ebpf程序开发中很常用的框架,理应支持不同架构的交叉编译。尤其是向内核态的ebpf程序本身就是直接通过clang的-target btf直接生成字节码,各个内核上的ebpf虚拟机大同小异…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
