當前位置: 妍妍網 > 碼農

零長度陣列沒有意義?那是你不懂!看Linux內核中怎麽高級玩它?

2024-04-20碼農

【幹貨】

【幹貨】

作者:曉亮Albert

C語言 零長度陣列,聽起來可能有點奇怪,因為它沒有分配記憶體空間,無法儲存數據。但實際上,零長度陣列在Linux內核中隨處可見。

零長度陣列的定義

首先,我們要明白什麽是零長度陣列。簡單來說,零長度陣列就是一個長度為0的陣列,也就是說不包含任何元素的陣列。零長度陣列在C99標準中引入,並在C11中得到進一步的支持。其定義很簡單,就是一個大小為0的陣列。例如:

int a[0];

在Linux內核中,零長度陣列通常不會直接這樣使用,而是作為結構體中最後一個元素,配合動態記憶體分配來使用。

零長度陣列在Linux內核中的套用案例

在Linux內核中,經常可以看到零長度陣列被用作結構體末尾的占位符,以表示結構體的可變長度部份。例如,一個表示網路套接字的struct sockaddr結構體可能如下所示:

structsockaddr {
sa_family_t sa_family; // 地址家族,如AF_INET, AF_UNIX等
char sa_data[14]; // 對於IPv4,這裏實際上只有12字節被使用
};

在這個例子中,sa_data欄位實際上是一個填充欄位,用於容納不同地址家族的地址數據。由於地址家族可能不同,所需的數據長度也可能不同,因此這裏使用了一個足夠大的固定長度陣列。然而,如果使用零長度陣列,程式碼會更加清晰:

structsockaddr {
sa_family_t sa_family; // 地址家族
char sa_data[0]; // 可變長度部份,實際使用時會動態分配
};

在實際套用中,內核程式碼會結合動態記憶體分配來設定需要的的 sa_data 長度,並填充相關的數據。零長度陣列可以與 kmalloc vmalloc 等記憶體分配函式結合使用,來實作這種動態分配,所以有人也把零長度陣列稱為柔性陣列。

如何具體實作結構體動態記憶體分配?

在Linux內核或其他C語言編寫的底層系統中,零長度陣列經常被用作靈活的數據結構的一部份,特別是在需要動態增長或縮小的陣列中。以下是一個簡單的範例,展示了如何在內核編程中使用零長度陣列來實作一個可變長度的整數陣列:

#include<linux/kernel.h> // 包含printk等內核函式
#include<linux/slab.h> // 包含kmalloc和kfree等記憶體管理函式
// 定義一個結構體,用於表示可變長度的整數陣列
structvariable_int_array {
size_t length; // 陣列當前長度
int data[0]; // 零長度陣列,實際數據儲存在這裏
};
// 建立一個新的可變長度整數陣列
struct variable_int_array *create_int_array(size_t initial_length){
// 分配記憶體,包括結構體本身和初始長度的整數陣列
structvariable_int_array *array = kmalloc(
sizeof(structvariable_int_array) + initial_length * sizeof(int),
GFP_KERNEL
);

if (!array) {
// 記憶體分配失敗
returnNULL;
}
// 初始化陣列長度
array->length = initial_length;
// 返回新建立的陣列
returnarray;
}
// 銷毀一個可變長度整數陣列
voiddestroy_int_array(struct variable_int_array *array){
if (!array) {
// 空指標檢查
return;
}
// 釋放記憶體
kfree(array);
}
// 向陣列中添加一個新的整數
voidadd_int_to_array(struct variable_int_array **array_ptr, int value){
structvariable_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;
}
// 打印陣列內容
voidprint_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");
}
// 內核模組初始化函式
staticint __init my_module_init(void){
structvariable_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);
return0;
}
// 內核模組結束函式
staticvoid __exitmy_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函式用來打印輸出出結構體中整數動態陣列成員值。微信搜尋公眾號:Linux技術迷,回復:linux 領取資料 。

下面具體來看看重點程式碼的實作。

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的陣列。但在編程中,它常常被用作一個占位符,或者作為一個結構體的最後一個元素,這樣可以在結構體中靈活地儲存更多的數據。

那麽,零長度陣列有什麽價值和意義呢?

  1. 靈活性 :零長度陣列允許我們在不知道具體需要多少儲存空間的情況下,先分配一個基本的結構體。這樣,我們可以在後續的程式執行中,根據需要動態地添加數據到這個零長度陣列中。這種靈活性對於處理可變大小的數據非常有用。

  2. 記憶體效率 :透過動態地分配記憶體給零長度陣列,我們可以避免一開始就分配過多的記憶體,這樣可以更加高效地利用記憶體資源。只有當我們確實需要額外的儲存空間時,才會分配額外的記憶體。

  3. 簡化程式碼 :在某些情況下,使用零長度陣列可以簡化程式碼結構。比如,我們可以將一些相關的數據都放在一個結構體中,而零長度陣列可以作為這個結構體的最後一個元素,用於儲存額外的數據。這樣,我們可以更方便地管理和操作這些數據。

<END>

點這裏👇關註我,記得標星呀~

往期精選:

GPT中文網站

可以在國內同ChatGPT直接進行對話,支持GPT4.0 和 AI繪圖,簡直太方便了,今天新註冊的直接送4.0提問次數 !

點「在看」的人都變好看了哦