2.1 什麼是指定初始化 在標準 C 中,當我們定義並初始化一個數組時,常用方法如下: 按照這種固定的順序,我們可以依次給 a[0] 和 a[8] 賦值。因為沒有對 a[9] 賦值,所以編譯器會將 a[9] 預設設置為0。當數組長度比較小時,使用這種方式初始化比較方便。當數組比較大,而且數組裡的非零 ...
2.1 什麼是指定初始化
在標準 C 中,當我們定義並初始化一個數組時,常用方法如下:
1 int a[10] = {0,1,2,3,4,5,6,7,8};
按照這種固定的順序,我們可以依次給 a[0] 和 a[8] 賦值。因為沒有對 a[9] 賦值,所以編譯器會將 a[9] 預設設置為0。當數組長度比較小時,使用這種方式初始化比較方便。當數組比較大,而且數組裡的非零元素並不連續時,這時候再按照固定順序初始化就比較麻煩了。
比如,我們定義一個數組 b[100],其中 b[10]、b[30] 需要初始化,如果還按照前面的固定順序初始化,{}中的初始化數據中間可能要填充大量的0,比較麻煩。
那怎麼辦呢?C99 標準改進了數組的初始化方式,支持指定任意元素初始化,不再按照固定的順序初始化。
int b[100] ={ [10] = 1, [30] = 2};
通過數組索引,我們可以直接給指定的數組元素賦值。除此之外,一個結構體變數的初始化,也可以通過指定某個結構體域直接賦值。
因為 GNU C 支持 C99 標準,所以 GCC 編譯器也支持這一特性。甚至早期不支持 C99,只支持 C89 的 GCC 編譯器版本,這一特性也被當作一個 GCC 編譯器的擴展特性來提供給程式員使用。
2.2 指定初始化數組元素
在 GNU C 中,通過數組元素索引,我們就可以給某個指定的元素直接賦值。
int b[100] = { [10] = 1, [30] = 2 };
在{ }中,我們通過 [10] 數組元素索引,就可以直接給 a[10] 賦值了。這裡有個細節註意一下,就是各個賦值之間用逗號 “,” 隔開,而不是使用分號“;”。
如果我們想給數組中某一個索引範圍的數組元素初始化,可以採用下麵的方式。
int main(void) { int b[100] = { [10 ... 30] = 1, [50 ... 60] = 2 }; for(int i = 0; i < 100; i++) { printf("%d ", a[i]); if( i % 10 == 0) printf("\n"); } return 0; }
在這個程式中,我們使用 [10 ... 30] 表示一個索引範圍,相當於給 a[10] 到 a[30] 之間的20個數組元素賦值為1。
GNU C 支持使用 ... 表示範圍擴展,這個特性不僅可以使用在數組初始化中,也可以使用在 switch-case 語句中。比如下麵的程式:
#include<stdio.h> int main(void) { int i = 4; switch(i) { case 1: printf("1\n"); break; case 2 ... 8: printf("%d\n",i); break; case 9: printf("9\n"); break; default: printf("default!\n"); break; } return 0; }
在這個程式中,當 case 值為2到8時,都執行相同的 case 分支,可以通過 case 2 ... 8: 的形式來簡化代碼。這裡同樣也有一個細節需要註意,就是 ... 和其兩端的數據範圍2和8之間也要空格,不能寫成2...8的形式,否則編譯就會通不過。
2.3 指定初始化結構體成員變數
跟數組類似,在標準 C 中,結構體變數的初始化也要按照固定的順序。在 GNU C 中我們也可以通過結構域來初始化指定某個成員。
struct student{ char name[20]; int age; }; int main(void) { struct student stu1={ "wit",20 }; printf("%s:%d\n",stu1.name,stu1.age); struct student stu2= { .name = "wanglitao", .age = 28 }; printf("%s:%d\n",stu2.name,stu2.age); return 0; }
在程式中,我們定義一個結構體類型 student,然後分別定義兩個結構體變數 stu1 和 stu2。初始化 stu1 時,我們採用標準 C 的初始化方式,即按照固定順序直接初始化。初始化 stu2 時,我們採用 GNU C 的初始化方式,通過結構功能變數名稱 .name 和 .age,我們就可以給結構體變數的某一個指定成員直接賦值。非常方便。
2.4 Linux 內核驅動註冊
在 Linux 內核驅動中,大量使用 GNU C 的這種指定初始化方式,通過結構體成員來初始化結構體變數。比如在字元驅動程式中,我們經常見到這樣的初始化:
static const struct file_operations ab3100_otp_operations = { .open = ab3100_otp_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, };
在驅動程式中,我們經常使用 file_operations 這個結構體變數來註冊我們開發的驅動,然後以回調的方式來執行我們驅動實現的相關功能。結構體 file_operations 在 Linux 內核中的定義如下:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };
結構體 file_operations 裡面定義了很多結構體成員,而在這個驅動中,我們只初始化了部分成員變數,通過訪問結構體的成員來指定初始化,非常方便。
2.5 指定初始化的好處
這種指定初始化方式,不僅使用靈活,而且還有一個好處就是:代碼易於維護。尤其是在 Linux 內核這種大型項目中,幾萬個文件,幾千萬的代碼量,當成百上千個文件都使用 file_operations 這個結構體類型來定義變數並初始化時,那麼一個很大的問題就來了:如果採用標準 C 那種按照固定順序賦值,當我們的 file_operations 結構體類型發生改變時,如添加成員、減少成員、調整成員順序,那麼使用該結構體類型定義變數的大量 C 文件都需要重新調整初始化順序,牽一發而動全身,想想這是多麼可怕!
我們通過指定初始化方式,就可以避免這個問題。無論file_operations 結構體類型如何變化,添加成員也好、減少成員也好、調整成員順序也好,都不會影響其它文件的使用。有了指定初始化,再也不用加班修改代碼了,媽媽再也不用擔心我們整日加班,不回家吃飯了,多好!