C语言柔性数组详解:让你的程序更灵活

news2024/12/28 2:54:21

柔性数组

    • 一、前言
    • 二、柔性数组的用法
    • 三、柔性数组的内存分布
    • 四、柔性数组的优势
    • 五、总结

一、前言

仔细观察下面的代码,有没有看出哪里不对劲?

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允许这种特殊的结构体存在。这样的结构体满足下面两个条件:

  1. 最后一个成员变量是一个大小可以变化的数组。
  2. 这个成员数组前面至少有另外一个成员变量。

我们称这个大小可以变化的成员数组为柔性数组

注意,柔性数组不能是结构体里唯一一个成员,下面的代码是不允许的:

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;
}

对于柔性数组的使用,在上面的例子中,可以总结出几个要点:

  1. malloc出来的大小是sizeof(struct S)加上柔性数组的大小,calloc同理。
  2. 扩容时realloc出来的新大小也是sizeof(struct S)加上柔性数组的新大小。
  3. 每次使用malloc和realloc等函数时,需要检查返回值,否则可能导致对NULL指针的解引用(这点是动态内存管理的常识了)。
  4. 一定要记得柔性数组的容量是多少,不要越界访问了,空间不够记得扩容。
  5. 记得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又指向了堆上的另一块空间,如下图:
在这里插入图片描述
这种实现方式和柔性数组的方式感觉差不多呀!都是在堆上有个结构体,结构体里有个大小可以变化的数组。那为什么非要搞出来个柔性数组的概念呢?那是因为,柔性数组有它独特的优势。

四、柔性数组的优势

前面我们先用柔性数组实现了一种效果,又不使用柔性数组实现了相似的效果,对比两种实现方式,我们可以做一些总结:

  1. 使用上:柔性数组malloc了一次,free了一次;不使用柔性数组要malloc两次,free两次。柔性数组的使用更加简单,不容易出错。如果不使用柔性数组,可能会忘记free掉结构体里的arr指针,导致内存泄漏。
  2. 效率上:柔性数组的存储空间是连续的,访问时效率更高。

所以,虽然有相似的效果,我更推荐使用柔性数组的方式。

五、总结

在这篇博客里,重点需要掌握以下几点:

  1. 如果结构体里最后一个成员变量是一个数组,并且大小可以变化,这个成员数组就叫做柔性数组。一个结构体里,除了柔性数组外必须至少有一个成员变量。
  2. 使用sizeof计算含有柔性数组的结构体大小时,只计算除柔性数组之外的空间大小。
  3. 使用柔性数组,比不使用柔性数组操作更加简单,不易出错,且效率更高。

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

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

相关文章

Sublime Text汉化,主打简单明了

在Sublime中设置中文的步骤如下&#xff1a; 1.打开Sublime Text&#xff0c;使用快捷键ShiftCtrlP&#xff08;MacOS下cmdShiftP&#xff09;&#xff0c;弹出查找栏。 2.在搜索框中输入关键字"install"&#xff0c;出现下拉选项&#xff0c;点击选择其中的"P…

Dubbo 应用切换 ZooKeeper 注册中心实例,流量无损迁移

首先思考一个问题&#xff1a;如果 Dubbo 应用使用 ZooKeeper 作为注册中心&#xff0c;现在需要切换到新的 ZooKeeper 实例&#xff0c;如何做到流量无损&#xff1f; 本文提供解决这个问题的一种方案。 场景 有两个基于 Dubbo 的微服务应用&#xff0c;一个是服务提供者&…

北京收录2023开学了《乡村振兴战略下传统村落文化旅游设计》中建博后许少辉八一新书

北京收录2023开学了《乡村振兴战略下传统村落文化旅游设计》中建博后许少辉八一新书

【业务功能篇92】微服务-springcloud-多线程-异步处理-异步编排-CompletableFutrue

三、CompletableFutrue 一个商品详情页 展示SKU的基本信息 0.5s展示SKU的图片信息 0.6s展示SKU的销售信息 1sspu的销售属性 1s展示规格参数 1.5sspu详情信息 1s 1.ComplatableFuture介绍 Future是Java 5添加的类&#xff0c;用来描述一个异步计算的结果。你可以使用 isDone方…

WPF工控机textbox获得焦点自动打开软键盘

1.通过nuget安装 osklib.wpf 2.在textbox getFoucs中敲入如下代码即可实现获得焦点弹出软键盘 private void txtPLC_IP_GotFocus(object sender, RoutedEventArgs e){try{// Osklib.OnScreenKeyboard.Close();Osklib.OnScreenKeyboard.Show();}catch (Exception ex){MessageB…

烟草企业物流管理信息系统的分析与设计(论文+源码)_kaic

摘要 在经济高速发展的今天&#xff0c;物流业已经成为支撑国民经济的基础性产业。作为一种新型服务业&#xff0c;物流业集仓储、运输、信息等为一体&#xff0c;发展成为复合型战略性产业。S烟草企业设计的物流管理信息系统利用B/S模式的三层结构&#xff0c;基于JSP技术和J…

13.108.Spark 优化、Spark优化与hive的区别、SparkSQL启动参数调优、四川任务优化实践:执行效率提升50%以上

13.108.Spark 优化 1.1.25.Spark优化与hive的区别 1.1.26.SparkSQL启动参数调优 1.1.27.四川任务优化实践&#xff1a;执行效率提升50%以上 13.108.Spark 优化&#xff1a; 1.1.25.Spark优化与hive的区别 先理解spark与mapreduce的本质区别&#xff0c;算子之间&#xff08;…

什么是架构,架构的本质是什么

不论是开发人员还是架构师&#xff0c;我们都一直在跟软件系统打交道&#xff0c;架构是在工作中出现最频繁的术语之一。那么&#xff0c;到底什么是架构&#xff1f;你可能有自己的答案&#xff0c;也有可能没有答案。对“架构”的理解需要我们不断在实践中思考、归纳、演绎&a…

说说Lambda架构

分析&回答 Lambda架构是由Storm的作者Nathan Marz提出的一个实时大数据处理框架。Marz在Twitter工作期间开发了著名的实时大数据处理框架Storm&#xff0c;Lambda架构是其根据多年进行分布式大数据系统的经验总结提炼而成。Lambda架构的目标是设计出一个能满足实时大数据系…

高教社杯数模竞赛特辑论文篇-2018年C题:基于 RFMT 模型的百货商场会员画像描绘(附获奖论文及代码实现)

目录 赛题 摘要 一、问题的重述 二、模型假设 三、变量说明 四、模型的建立与求解 4.1 数据预处理 4.2 问题一的模型建立与求解 4.2.1 建模思路 4.2.2 模型建立 4.2.3 模型的求解与结果分析 4.3 问题二的模型建立与求解 4.3.1 建模思路 4.3.2 模型建立 4.3.3 模…

污水厂数字孪生 | 3D可视化管理系统助力污水企业数字化管理

随着城市化进程的不断加快&#xff0c;污水处理成为了城市环境保护的重要组成部分。传统的污水处理方式往往存在诸多问题&#xff0c;如信息不对称、安全隐患等。为了解决这些问题&#xff0c;污水处理3D可视化管控平台应运而生&#xff0c;它通过结合数字孪生技术和远程指导技…

详解Python argparse ---命令行选项、参数和子命解析器

详解argparse模块 一、 模块简介二、使用步骤三、ArgumentParser(&#xff09;参数四、add_argument&#xff08;&#xff09;参数详解五、示例 一、 模块简介 argparse模块使编写用户友好的命令行界面变得容易。该程序定义了它需要什么参数&#xff0c;argparse将找出如何从s…

使用C语言计算1/1-1/2+1/3-1/4+...+1/99-1/100

观察算式&#xff0c;发现分子都是1&#xff0c;分母从1~100&#xff0c;所以可以使用for循环产生1~100之间的数。 另一个问题是&#xff0c;如何产生正负交替的符号&#xff1f;很简单&#xff0c;这个符号本质上就是往每一项前面乘一个系数&#xff1a;一或者负一。所以只需…

纽扣电池/锂电池UN38.3安全检测报告

根据规章要求&#xff0c;航空公司和机场货物收运部门应对锂电池进行运输文件审查&#xff0c;重要的是每种型号的锂电池UN38.3安全检测报告。该报告可由的三方检测机构。如不能提供此项检测报告&#xff0c;将禁止锂电池进行航空运输. UN38.3包含产品&#xff1a;1、 锂电池2…

AI建模 | 物体三维重建的高效方法

三维重建是将客观世界中的物体在虚拟空间表达出来&#xff0c;在大众视野中&#xff0c;物品三维重建最直观的应用当属虚拟仿真和VR/AR导航。其实在学科专业领域&#xff0c;三维重建已经更早地应用在高精地图、测绘系统、城市规划等领域。 科技发展的终极方向应当是普适性&am…

dll修复精灵下载方法,完美解决电脑d3dx9-d3dx11dll文件丢失方法

大家好&#xff01;今天&#xff0c;我将为大家带来一场关于d3dx9_43.dll丢失的6种解决方法的演讲。希望通过这次演讲&#xff0c;能够帮助大家解决在电脑使用过程中遇到的问题&#xff0c;提高我们的生活和工作效率。 首先&#xff0c;让我们来了解一下d3dx9_43.dll是什么文件…

《QDebug 2023年8月》

一、Qt Widgets 问题交流 1.获取 QWidget 当前所在屏幕区域 本来以为 QWidget 的 screen() 接口返回的是组件自己所在屏幕的 QSreen&#xff0c;实测是所属 Window 所在的屏幕&#xff0c;如果 Window 跨屏了两者所属屏幕可能就不是同一个。 获取 QWidget 当前所在屏幕区域可…

jmeter单接口和多接口测试

最近接触到了多接口串联&#xff0c;接口串联的技术会在其他帖子有说明&#xff0c;其核心技术点就是通过正则表达式和变量来实现接口的关联。目前为止呢笔者用到的地方还只有一个&#xff0c;就是关于session保持的时候。但是看到很多资料都说测试过程中经常遇到b接口需要用a接…

IDA Pro反汇编工具下载安装使用

一、前言 IDA Pro&#xff08;Interactive Disassembler Professional&#xff09;简称“IDA”&#xff0c;是Hex-Rays公司出品的一款交互式反汇编工具&#xff0c;是目前最棒的一个静态反编译软件&#xff0c;为众多0day世界的成员和ShellCode安全分析人士不可缺少的利器。ID…

R3LIVE项目实战(5) — R3LIVE数据采集与时间同步

目录 1 R3LIVE数据采集运行步骤 1.1 录制数据集 1.2 修改config下对应的配置文件 1.3 启动相机和雷达节点 1.4 运行R3LIVE与播包 2 R3LIVE在线运行 1 R3LIVE数据采集运行步骤 1.1 录制数据集 采集数据需要注意的一点是&#xff0c;不同传感器之间的时间同步问题&#x…