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
长度,并填充相关的数据。零长度数组可以与
kmalloc
、
vmalloc
等内存分配函数结合使用,来实现这种动态分配,所以有人也把零长度数组称为柔性数组。
如何具体实现结构体动态内存分配?
在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)函数,这个函数就是将一个新的整数值动态添加到数组中,这也是最麻烦的过程。