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

发布时间:2024-04-02  

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

本文引用地址:

零长度的定义

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

int a[0];

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

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

在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)函数,这个函数就是将一个新的整数值动态添加到数组中,这也是最麻烦的过程。

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

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

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

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

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

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

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

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

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

总结

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

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

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

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

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

文章来源于:电子产品世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>