[Linux Kernel慢慢學]探討Designated Initializers

Posted by John on 2020-10-06
Words 1.3k and Reading Time 5 Minutes
Viewed Times

前言

在看別人c語言寫的程式碼中,有時可能會別人在struct會是array中使用到了”.”(點或是英文叫做dot)來進行初始化,例如下面這段struct student的宣告:

struct student s = {
.id = 1;
.name = "Just John";
.height = 180;
}

又或是,在linux kernel code中你可以看到一堆這樣的用法

/* drivers/clk/clk-aspeed.c */

static const struct aspeed_clk_soc_data ast2500_data = {
.div_table = ast2500_div_table,
.eclk_div_table = ast2500_eclk_div_table,
.mac_div_table = ast2500_mac_div_table,
.calc_pll = aspeed_ast2500_calc_pll,
};

可是如果沒用過的人可能就不知道這是什麼意思,然後這個偏偏又不是很好下關鍵字去google,因為”.”很容易被關聯到其他的問題,所以這篇就專門針對這個來進行介紹。

Designated Initializers

上面的用法其實稱作Designated Initializers,在Using the GNU Compiler Collection (GCC)的6.29 Designated Initializers中你可以看到詳盡的介紹,下面透過中文的方式針對裡面比較重要的內容進行簡介。

在C90以前的規格,原本規定是必須按照固定的順序來對structure或array進行初始化的。

不過在C99開始,你可以透過指定array indeices (or structure field names in strure)來進行初始化。

Array

首先針對Array來介紹,例如你可以透過[index] = 來初始化陣列的部分元素

int a[6] = { [4] = 50, [5] = 100};

上面的寫法和下列寫法相等

int a[6] = { 0, 0, 50, 0, 100, 0 };

其實這裡還扯到了一件事情: array沒有被給值的elements都會是0嗎?

這個問題跟我們常用的陣列初始化寫法本質是一樣的事情,只是可能都沒有想過,考慮以下的陣列宣告

int arr[100] = {0};

為什麼這樣就是讓arr的100個element都變成了0呢?

這樣的寫法實際上只給予了arr[0]值,其他的arr都是沒有被給值初始化的,有些人會稱這個叫做Partical Initialization

  • 不過這個詞其實並不標準,Standard並沒有定義什麼是Partical Initialization,,所以還是盡量不要用會比較好,以免被誤導

對於上面的狀況,在C99 Standard 6.7.8.21中是這樣寫的:

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

也就是說,當我們宣告一個struct, array or string (with known size)時,但給定的initializers數量小於實際上該struct, array, string的大小時,剩餘的部分會被當作static storage duration來初始化,而static variable的initialization是0

  • 大家應該都有印象static和global variable初始化的值都是0,其實這與variable在memory layout中放置的位置有關,但這裡就不繼續往下追了

好了我們把話題扯回Designated Initializers,對於Array,GNU extension還允許你透過
[first … last] = value
的方式來宣告

int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };

注意此時array的長度會是最大值的index+1。

此外,在宣告時如果沒有給定index的initializer,element會根據他旁邊的index來決定他的index,例如:

int a[6] = { [1] = v1, v2, [4] = v4 };

等價於

int a[6] = { 0, v1, v2, 0, v4, 0 };

最後來看兩個例子,示範Array在C99的designator initization中可以達到什麼樣的好處

Example 1

假設我們原本有以下的資料:

enum data_name {
CHINESE = 0,
ENGLISH = 1,
KOREAN = 2
}

char *say_hello[] = {
"你好",
"hello",
"안녕하십니까"
}

在這樣的設計中,我們可以透過say_hello[CHINESE]來取得中文版的你好字串,但當enum的順序一有變動,對應的say_hello就要整個重改。

如果我們改成這樣的寫法:

char *say_hello[] = {
[CHINESE] = "你好",
[ENGLISH] = "hello",
[KOREAN] = "안녕하십니까"
};

就不會有上述問題了,如此為設計上增添了許多靈活性。

Example 2

在要做字元表的時候,使用這種方式會特別方便,我們可以直接給予char index來進行初始化:

int whitespace[256]
= { [' '] = 1, ['\t'] = 1, ['\h'] = 1,
['\f'] = 1, ['\n'] = 1, ['\r'] = 1 };

Structure

對於structure,C99允許你透過指定field name的方式來對該struct進行初始化,這樣的好處免去了宣告時一定要按照順序的麻煩,並且未來更改struct時不用整個宣告重寫

struct point {
int x;
int y;
}

struct point p = {.x = 1, .y = 2};

上述的p宣告效果和

struct point p = { x = 1, y = 2};

效果相同

Omitted fields are implicitly initialized the same as for objects that have static storage duration.

沒有宣告的fields一樣會被初始化成0(與陣列相同)。

並且這個方法也可以被用在union,不過這裡就不詳述,有興趣的可以再去看。

Combine array & struct

把array和struct混合起來一起用就變成

struct point ptarray[10] = { 
[2].y = yv2,
[2].x = xv2,
[0].x = xv0 };

結語

最後,補充一些小部分:

If the same field is initialized multiple times, or overlapping fields of a union are initialized, the value from the last initialization is used

如果在初始化時,有相同的field被重複initialize,則最後使用最後一次的initization。

The ‘[index]’ or ‘.fieldname’ is known as a designator.

最後,[index]和.fieldname都叫做designator,這也是為什麼這個方法叫做designator initization。

References


>