2.1 什么是指定初始化
在标准 C 中,当我们定义并初始化一个数组时,常用方法如下:
1 int a[10] = { 0,1,2,3,4,5,6,7,8};
比如,我们定义一个数组 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 };
如果我们想给数组中某一个索引范围的数组元素初始化,可以采用下面的方式。
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; }
GNU C 支持使用 ... 表示范围扩展,这个特性不仅可以使用在数组初始化中,也可以使用在 switch-case 语句中。比如下面的程序:
#includeint 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;}
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;}
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,};
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 };
2.5 指定初始化的好处
这种指定初始化方式,不仅使用灵活,而且还有一个好处就是:代码易于维护。尤其是在 Linux 内核这种大型项目中,几万个文件,几千万的代码量,当成百上千个文件都使用 file_operations 这个结构体类型来定义变量并初始化时,那么一个很大的问题就来了:如果采用标准 C 那种按照固定顺序赋值,当我们的 file_operations 结构体类型发生改变时,如添加成员、减少成员、调整成员顺序,那么使用该结构体类型定义变量的大量 C 文件都需要重新调整初始化顺序,牵一发而动全身,想想这是多么可怕!
我们通过指定初始化方式,就可以避免这个问题。无论file_operations 结构体类型如何变化,添加成员也好、减少成员也好、调整成员顺序也好,都不会影响其它文件的使用。有了指定初始化,再也不用加班修改代码了,妈妈再也不用担心我们整日加班,不回家吃饭了,多好!