零长度数组没有意义?那是你不懂!看Linux内核中怎么高级玩它?

C语言与CPP编程 2024-10-29 09:00

作者:晓亮Albert


C语言零长度数组,听起来可能有点奇怪,因为它没有分配内存空间,无法存储数据。但实际上,零长度数组在Linux内核中随处可见。

零长度数组的定义

首先,我们要明白什么是零长度数组。简单来说,零长度数组就是一个长度为0的数组,也就是说不包含任何元素的数组。零长度数组在C99标准中引入,并在C11中得到进一步的支持。其定义很简单,就是一个大小为0的数组。例如:

int a[0];

在Linux内核中,零长度数组通常不会直接这样使用,而是作为结构体中最后一个元素,配合动态内存分配来使用。

零长度数组在Linux内核中的应用案例

在Linux内核中,经常可以看到零长度数组被用作结构体末尾的占位符,以表示结构体的可变长度部分。例如,一个表示网络套接字的struct sockaddr结构体可能如下所示:

struct sockaddr {  
sa_family_t sa_family; // 地址家族,如AF_INET, AF_UNIX等
char sa_data[14]; // 对于IPv4,这里实际上只有12字节被使用
};

在这个例子中,sa_data字段实际上是一个填充字段,用于容纳不同地址家族的地址数据。由于地址家族可能不同,所需的数据长度也可能不同,因此这里使用了一个足够大的固定长度数组。然而,如果使用零长度数组,代码会更加清晰:

struct sockaddr {  
sa_family_t sa_family; // 地址家族
char sa_data[0]; // 可变长度部分,实际使用时会动态分配
};

在实际应用中,内核代码会结合动态内存分配来设置需要的的sa_data长度,并填充相关的数据。零长度数组可以与kmallocvmalloc等内存分配函数结合使用,来实现这种动态分配,所以有人也把零长度数组称为柔性数组。

如何具体实现结构体动态内存分配?

在Linux内核或其他C语言编写的底层系统中,零长度数组经常被用作灵活的数据结构的一部分,特别是在需要动态增长或缩小的数组中。以下是一个简单的示例,展示了如何在内核编程中使用零长度数组来实现一个可变长度的整数数组:

#include   // 包含printk等内核函数  
#include // 包含kmalloc和kfree等内存管理函数

// 定义一个结构体,用于表示可变长度的整数数组
struct variable_int_array {
size_t length; // 数组当前长度
int data[0]; // 零长度数组,实际数据存储在这里
};

// 创建一个新的可变长度整数数组
struct variable_int_array *create_int_array(size_t initial_length) {
// 分配内存,包括结构体本身和初始长度的整数数组
struct variable_int_array *array = kmalloc(
sizeof(struct variable_int_array) + initial_length * sizeof(int),
GFP_KERNEL
);


if (!array) {
// 内存分配失败
return NULL;
}

// 初始化数组长度
array->length = initial_length;

// 返回新创建的数组
return array;
}

// 销毁一个可变长度整数数组
void destroy_int_array(struct variable_int_array *array) {
if (!array) {
// 空指针检查
return;
}

// 释放内存
kfree(array);
}

// 向数组中添加一个新的整数
void add_int_to_array(struct variable_int_array **array_ptr, int value) {
struct variable_int_array *array = *array_ptr;
size_t new_length = array->length + 1;

// 分配新的内存块,包含扩展后的数组
array = kmalloc(
sizeof(struct variable_int_array) + new_length * sizeof(int),
GFP_KERNEL
);

if (!array) {
// 内存分配失败
printk(KERN_ERR "Failed to extend the integer array.\n");
return;
}

// 复制旧数组的值到新数组
memcpy(array->data, (*array_ptr)->data, array->length * sizeof(int));

// 添加新值
array->data[new_length - 1] = value;


// 更新数组长度
array->length = new_length;

// 释放旧数组
kfree(*array_ptr);

// 更新指向数组的指针
*array_ptr = array;
}

// 打印数组内容
void print_int_array(struct variable_int_array *array) {
for (size_t i = 0; i < array->length; i++) {
printk(KERN_INFO "%d ", array->data[i]);
}
printk(KERN_INFO "\n");
}

// 内核模块初始化函数
static int __init my_module_init(void) {
struct variable_int_array *my_array = create_int_array(2);

if (!my_array) {
// 处理错误
return -ENOMEM;
}

// 添加一些值
add_int_to_array(&my_array, 10);
add_int_to_array(&my_array, 20);

// 打印数组
print_int_array(my_array);

// 销毁数组
destroy_int_array(my_array);

return 0;
}

// 内核模块退出函数
static void __exit my_module_exit(void) {
// 清理工作(如果有的话)
}

// 注册模块初始化和退出函数
module_init(my_module_init);
module_exit(my_module_exit);

// 定义模块许可证
MODULE_LICENSE("GPL");

在这个例子中,忽略内核模块相关部分,重点看结构体variable_int_array相关几个函数。

我们定义了一个名为variable_int_array的结构体,它包含一个length字段和一个零长度数组data。使用create_int_array函数来分配内存并初始化这个结构体,同时使用destroy_int_array函数来释放内存。add_int_to_array函数允许我们向数组中添加新的整数,它会动态地重新分配内存以容纳新增加的元素。最后,print_int_array函数用来打印输出出结构体中整数动态数组成员值。

下面具体来看看重点代码的实现。

create_int_array函数创建一个新的可变长度整数数组的结构体variable_int_array,函数形参initial_length是要创建数组初始长度。第13行使用kmalloc动态分配结构体初始内存空间,这里包括结构体本身和初始长度为initial_length的整数数组空间。第24行就是把initial_length,也即是初始数据长度值存到结构体length成员中,因为长度不是0了而是initial_length。

destroy_int_array就是调用kfree释放上面创建的内存空间,这个比较简单。

重点看看add_int_to_array(struct variable_int_array **array_ptr, int value)函数,这个函数就是将一个新的整数值动态添加到数组中,这也是最麻烦的过程。

第一个形参是结构体array_ptr,是个二级指针,指向旧的结构体内存首地址,注意这个指针变量后面新分配内存空间地址要存入其中。第二个形参value是被添加的新的整数值。

第43行是将旧的结构体首地址存到array指针中。

第44行new_length暂时保存数组长度。

第47行是分配新的内存空间,并将首地址存入array变量,注意从此以后array指向新空间。因为数组新加了一个整数,所以空间变大,要重新分配,新分配的空间大小包括之前旧的结构体长度和新添加的一个整数的空间大小。

第59行是将旧的数组数据拷贝到新的数组空间中。

第62行就是新的整数值添加到新数组空间最后一个位置,到此数组空间数据更新完成。

第66行更新结构体的length成员为new_length,其实就是加了个1。

第69行,释放之前旧结构体的所有内存,因为长度增加分配了新内存了。

第72行就是将新空间地址赋给array_ptr指针变量,这是让指向旧结构体首地址的指针指向新的结构体首地址了,到此就结束了。

总结

简单来说,零长度数组就是一个长度为0的数组。但在编程中,它常常被用作一个占位符,或者作为一个结构体的最后一个元素,这样可以在结构体中灵活地存储更多的数据。

那么,零长度数组有什么价值和意义呢?

灵活性:零长度数组允许我们在不知道具体需要多少存储空间的情况下,先分配一个基本的结构体。这样,我们可以在后续的程序执行中,根据需要动态地添加数据到这个零长度数组中。这种灵活性对于处理可变大小的数据非常有用。

内存效率:通过动态地分配内存给零长度数组,我们可以避免一开始就分配过多的内存,这样可以更加高效地利用内存资源。只有当我们确实需要额外的存储空间时,才会分配额外的内存。

简化代码:在某些情况下,使用零长度数组可以简化代码结构。比如,我们可以将一些相关的数据都放在一个结构体中,而零长度数组可以作为这个结构体的最后一个元素,用于存储额外的数据。这样,我们可以更方便地管理和操作这些数据。

EOF

你好,我是飞宇。日常分享C/C++、计算机学习经验、工作体会,欢迎点击此处查看我以前的学习笔记&经验&分享的资源。

我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。

欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会

加个微信,打开另一扇窗

经常遇到有读者后台私信想要一些编程学习资源,这里分享 1T 的编程电子书、C/C++开发手册、Github上182K+的架构路线图、LeetCode算法刷题笔记等精品学习资料,点击下方公众号会回复"编程"即可免费领取~

感谢你的分享,点赞,在看三  

C语言与CPP编程 C语言/C++开发,C语言/C++基础知识,C语言/C++学习路线,C语言/C++进阶,数据结构;算法;python;计算机基础等
评论
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 73浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 66浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 72浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 166浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 110浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 66浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 42浏览
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 79浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 76浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 73浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 171浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦