C语言——柔性数组

1、柔性数组是什么

在C语言中,柔性数组成员(Flexible Array Member,简称FAM)是C99标准中引入的一种结构体成员,用于表示一个大小可变的数组。它是结构体的最后一个成员,不像普通的数组,没有固定的长度。这使得结构体能够以一种非常灵活的方式来处理可变长度的数组数据。

含有柔性数组成员的结构体的声明方式:

typedef struct Example {
	int length;
	int data[0];
}flexible_array;

或者

typedef struct Example {
	int length;
	int data[];
}flexible_array;

第一种方式有的编译器可能报错。

2、柔性数组成员的特点

  • 必须是结构体的最后一个成员。
  • 柔性数组成员之前必须有至少一个其他成员。
  • 在结构体定义时,柔性数组成员不占用内存空间(其大小被声明为零或为空维度的数组)。
#include <stdio.h>

typedef struct Example {
	int length;
	int data[];
}flexible_array;

int main()
{
	printf("%zu\n", sizeof(flexible_array));
	return 0;
}

运行结果:

可以看到这里的结构体大小只有一个整形的大小,这时表明在结构体定义时,柔性数组是不占用内存空间的。

  • 实际的数组大小是在运行时决定的,包含柔性数组的结构体在使用时使用动态分配,在分配时应大于结构体的大小,以适应该柔性数组的预期大小。

3、使用示例

使用malloc函数给柔性数组元素分配空间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef struct Example {
    int length;
    int data[]; // 柔性数组成员
} flex_array;

int main()
{
    // 创建一个长度为10的柔性数组
    int n = 10;
    flex_array* array = (flex_array*)malloc(sizeof(flex_array) + sizeof(int) * n);
    if (array == NULL)
    {
        printf("%s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    array->length = n;

    // 使用柔性数组
    for (int i = 0; i < array->length; i++)
    {
        array->data[i] = i;
    }

    // 打印数据
    for (int i = 0; i < array->length; i++)
    {
        printf("%d ", array->data[i]);
    }
    printf("\n");

    // 释放内存
    free(array);
    //指着置空
    array = NULL;
    return 0;
}

运行结果:

在这个例子中,flex_array 结构体中有一个长度为length的柔性数组 data。分配给这个结构体的内存比其静态部分更大,足以容纳lengthint类型的元素。使用malloc时,我们需要计算出足够的空间来存储结构体的固定部分(这里是int length)加上柔性数组需要的空间(这里是sizeof(int) * n)。

使用realloc函数重新给柔性数组元素分配空间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef struct Example {
	int length;
	int data[]; // 柔性数组成员
} flex_array;

int main()
{
	// 创建一个长度为10的柔性数组
	int n = 10;
	flex_array* array = (flex_array*)malloc(sizeof(flex_array) + sizeof(int) * n);
	if (array == NULL)
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}
	array->length = n;

	// 使用柔性数组
	for (int i = 0; i < array->length; i++)
	{
		array->data[i] = i;
	}

	// 打印数据
	for (int i = 0; i < array->length; i++)
	{
		printf("%d ", array->data[i]);
	}
	printf("\n");

	//扩容
	n = 20;
	flex_array* temp = (flex_array*)realloc(array, sizeof(int) + n * sizeof(int));//使用临时的指针变量,防止内存泄露
	if (temp == NULL)
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	array = temp;//开辟成功则赋值给之前的指针
	temp = NULL;
	array->length = n;//更新数组长度元素

	for (int i = 10; i < array->length; i++)
	{
		array->data[i] = i;
	}
	for (int i = 0; i < array->length; i++)
	{
		printf("%d ", array->data[i]);
	}
	printf("\n");

	// 释放内存
	free(array);
	//指着置空
	array = NULL;
	return 0;
}

运行结果:

4、与含有指针的结构体的比较

1)含有指针的结构体

我们发现,如果目的只是想让结构体中多一个大小可以变化的数组,为什么不是在结构体中加一个指针,然后将动态内存分配的内存块的指针赋值给这个指针来使用呢,就像下面这样:

i)方式1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef struct Example {
	int length;
	int* data;
}Example;

int main()
{
	//在堆区创建和初始化结构体变量,因为整形指针指向的空间在堆区,为了保证这些变量都在堆区
	Example* array = (Example*)malloc(sizeof(Example));

	if (array == NULL)//未开辟成功的情况
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	//在对结构体变量开辟成功后,再对整型指针指向的空间进行开辟
	array->data = (int*)malloc(10 * sizeof(int));

	if (array->data == NULL)//未开辟成功的情况
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	//开辟成功
	array->length = 10;
	for (int i = 0; i < array->length; i++)
	{
		array->data[i] = i;
	}
	for (int i = 0; i < array->length; i++)
	{
		printf("%d ", array->data[i]);
	}
	printf("\n");
	
	//释放空间
	//先对整形指针指向的内存块释放,然后将整型指针置空
	free(array->data);
	array->data = NULL;

	//后对结构体变量进行释放,然后将指针置空
	free(array);
	array = NULL;
	return 0;
}

运行结果:

ii)方式2

这里也可以不将结构体变量在堆区中开辟:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef struct Example {
	int length;
	int* data;
}Example;

int main()
{
	Example array = {0,NULL};//不在堆中开辟,而是在作为局部变量在栈中开辟

	//对内存块进行开辟
	array.data = (int*)malloc(10 * sizeof(int));

	if (array.data == NULL)//未开辟成功的情况
	{
		printf("%s\n", strerror(errno));
		return EXIT_FAILURE;
	}

	//开辟成功
	array.length = 10;
	for (int i = 0; i < array.length; i++)
	{
		array.data[i] = i;
	}
	for (int i = 0; i < array.length; i++)
	{
		printf("%d ", array.data[i]);
	}
	printf("\n");
	
	//释放空间
	free(array.data);
	array.data = NULL;

	return 0;
}

运行结果:

这两种方式同样可以实现用realloc改变数组的大小。

2)两者有什么不同

i)内存分配效率

使用柔性数组,你只需要进行一次内存分配。结构体和数据都在一个连续的内存块中。如果使用指针,你通常需要两次分配:一次用于结构体本身,另一次用于数组。这不仅涉及两个独立的内存操作,还可能导致额外的内存碎片。

对于柔性数组:
typedef struct Example {
	int length;
	int data[]; // 柔性数组成员
} flex_array;

可以发现这里的空间是连续的,在释放时只需一次释放。

对于包含指针的结构体:
typedef struct Example {
	int length;
	int* data;
}Example;

对于第一种方式:

可以发现这里的空间是不连续的,在释放时需要两次释放才能完成(对于结构体变量也在堆区中开辟的情况)。

对于第二种方式:

ii)空间效率

柔性数组不需要存储数组数据的指针,因此它节省了存储指针本身所需的空间。这在结构体实例很多时尤其重要。

5、为什么柔性数组成员必须是结构体的最后一个成员

在结构体内部,所有成员都有固定的偏移量。如果柔性数组不是最后一个成员,那么在它之后的任何成员的位置将无法确定,因为柔性数组的大小在编译时是未知的。放在最后确保所有其他成员都有固定的偏移。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spring boot运行过程中动态加载Controller

1.被加载的jar代码 package com.dl;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class App {public static void main(String[] args) {SpringApplication.run(A…

stm32f103c8t6学习笔记(学习B站up江科大自化协)-UNIX时间戳、BKPRTC

UNIX时间戳 UNIX时间戳最早是在UNIX系统使用的&#xff0c;所以叫做UNIX时间戳&#xff0c;之后很多由UNIX演变而来的系统也继承了UNIX时间戳的规定&#xff0c;目前linux&#xff0c;windows&#xff0c;安卓这些操作系统的底层计时系统都是用UNIX时间戳 时间戳这个计时系统和…

http请求报文的组成与作用?

http请求报文的组成与作用&#xff1f; 一、http 的请求报文组成二、请求行&#xff08;Request Line&#xff09;三、请求头部&#xff08;Request Headers&#xff09;四、请求体&#xff08;Request Body&#xff09;五、响应头部 &#xff08;Response Headers &#xff09…

部署YUM仓库和NFS共享存储服务

目录 1. YUM仓库服务 1.1 YUM概述 1.2 准备安装源 1.3 yum在线源替换方法 2.制作YUM源 2.1制作ftp源 3.yum软件包的下载方式 4.NFS共享存储服务 4.1 NFS 4.2 NFS网络文件系统 4.3 NFS配置 1. YUM仓库服务 1.1 YUM概述 yum是一个基于RPM包&#xff08;是Red-Ha…

MAC 本地搭建Dify环境

Dify 介绍 Dify 是一款开源的大语言模型(LLM) 应用开发平台。它融合了后端即服务&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使开发者可以快速搭建生产级的生成式 AI 应用。即使你是非技术人员&#xff0c;也能参与到 AI 应用的定义和数据运营过…

运维的利器–监控–zabbix–第二步:建设–汉化补丁--导致乱码问题

文章目录 问题原因解决方法 问题 点击对应主机的【图形】即可看到以下乱码情况 原因 上述的图标数据&#xff0c;下面的小白框表示乱码含义&#xff0c;是因为我们改了zabbix的 语言为中文 解决方法 服务器需要安装字体 [rootzabbix-server01 ~]#yum -y install wqy-mic…

go设计模式之抽象工厂模式

抽象工厂模式 提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 工厂方法模式通过引入工厂等级结构&#xff0c;解决了简单工厂模式中工厂类职责太重的问题&#xff0c;但由于工厂方法模式中的每个工厂只生产一类产品&#xff0c;可能会导致…

maven-依赖管理

依赖配置 https://mvnrepository.com/?__cf_chl_rt_tkvRzDsumjmJ_HF95MK4otu9XluVRHGqAY5Wv4UQYETR8-1714103058-0.0.1.1-1557 <dependencies><dependency><groupId></groupId><artifactId></artifactId><version></version>…

精确测量地面沉降:静力水准仪的应用

地面沉降是一个全球性的地质问题&#xff0c;它可能对建筑物、道路和地下设施造成严重的损害。因此&#xff0c;对地面沉降进行精确测量和监测至关重要。静力水准仪作为一种先进的测量设备&#xff0c;为地面沉降的精确测量提供了有效手段。本文将探讨静力水准仪在地面沉降测量…

Dokcer容器分布式搭建LNMP+wordpress论坛

目录 引言 一、架构环境 二、搭建容器 &#xff08;一&#xff09;自定义网络 &#xff08;二&#xff09;搭建nginx容器 1.文件准备 2.查看与编辑文件 3.生成镜像 4.创建容器 &#xff08;三&#xff09;搭建MySQL容器 1.文件准备 2.查看与编辑文件 3.生成镜像 …

版本控制系统-Git

目录 1. Git简介 2. 下载及安装 3.命令行操做 3.1全局设置 3.2初始化仓库 3.3提交代码 3.4查看提交历史 3.5推送代码 3.6拉取合并代码 3.7克隆仓库 3.8. 配置忽略文件 3.9. 凭据管理 4. GUI工具操作 4.1. 全局设置 4.2. 初始化仓库 4.3. 提交代码 输入提交日志…

【linux-1-Ubuntu常用命令-vim编辑器-Vscode链接ubuntu远程开发】

目录 1. 安装虚拟机Vmare和在虚拟机上安装Ubuntu系统&#xff1a;2. 常用的Ubuntu常识和常用命令2.1 文件系统结构2.2 常用命令2.3 vim编辑器 3. Ubuntu能联网但是ping不通电脑&#xff1a;4. Windows上安装VScode链接ubuntu系统&#xff0c;进行远程开发&#xff1a; 1. 安装虚…

uni-app - 使用地图功能打包安卓apk的完美流程以及重要的注意事项(带您一次打包成功)

在移动应用开发中&#xff0c;地图功能是一个非常常见且实用的功能&#xff0c;可以帮助用户快速定位并浏览周边信息。而在uni-app开发中&#xff0c;使用地图功能也是一项必备技能。本文将介绍uni-app使用地图功能打包安卓apk的注意事项&#xff0c;帮助开发者顺利完成地图功能…

每日OJ题_DFS爆搜深搜回溯剪枝①_力扣784. 字母大小写全排列

目录 力扣784. 字母大小写全排列 解析代码1_path是全局变量 解析代码2_path是函数参数 力扣784. 字母大小写全排列 784. 字母大小写全排列 难度 中等 给定一个字符串 s &#xff0c;通过将字符串 s 中的每个字母转变大小写&#xff0c;我们可以获得一个新的字符串。 返回…

Linux环境下的编译和调试

本文目录 一、编译1. gcc/g编译器2. gcc/g安装3. 代码编译过程4. gcc编译 二、调试1. 下载gdb调试器2. gdb 调试器使用步骤 一、编译 1. gcc/g编译器 对于.c 格式的 C 文件&#xff0c;可以采用 gcc 或 g编译。 对于.cc、.cpp 格式的 C文件&#xff0c;应该采用 g进行编译。 …

第一个Cython程序-helloworld

Cython是Python的一个模块&#xff0c;可以将python语言“翻译”成C语言。 如何安装&#xff1f; python -m pip install Cython -i https://pypi.tuna.tsinghua.edu.cn/simple/ 新建两个文件helloworld.pyx和setup.py。 helloworld.pyx print("hello world")setu…

【学习vue 3.x】(五)VueRouter路由与Vuex状态管理

文章目录 章节介绍本章学习目标 路由的基本搭建与嵌套路由模式vue路由的搭建嵌套路由模式 动态路由模式与编程式路由模式动态路由模式编程式路由 命名路由与命名视图与路由元信息命名路由命名视图路由元信息 路由传递参数的多种方式及应用场景路由传参 详解route对象与router对…

ubuntu开启message文件

环境&#xff1a;ubuntu 20.04 1、首先需要修改 /etc/rsyslog.d/50-default.conf 文件&#xff1b;源文件中message被注释&#xff0c;如下图&#xff1a; 2、打开注释&#xff1a; 3、重启服务 systemctl restart rsyslog.service 如此即可&#xff01;

OFDM802.11a的FPGA实现(五)卷积编码器的FPGA实现与验证(含verilog代码和matlab代码)

目录 1.前言2.卷积编码2.1卷积编码基本概念2.2 802.11a卷积编码器2.3 卷积编码模块设计2.4 Matlab设计与ModelSim仿真验证 1.前言 前面一节完成了扰码器的FPGA设计与Matlab验证&#xff0c;这节继续对卷积编码器进行实现和验证。 2.卷积编码 2.1卷积编码基本概念 卷积码编码器…

Aiseesoft Data Recovery for Mac:专业数据恢复软件

Aiseesoft Data Recovery for Mac是一款高效且专业的数据恢复软件&#xff0c;专为Mac用户量身打造。 Aiseesoft Data Recovery for Mac v1.8.22激活版下载 无论是由于误删、格式化还是系统崩溃等原因导致的数据丢失&#xff0c;Aiseesoft都能帮助您快速找回。 它采用先进的扫描…