【嵌入式】C語言高級編程-數組和結構體初始化(02)

00. 目錄

01. 初始化概述

在標準 C 中,當我們定義並初始化一個數組時,常用方法如下:

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 a[100] ={ [10] = 1, [30] = 2};

通過數組索引,我們可以直接給指定的數組元素賦值。除此之外,一個結構體變量的初始化,也可以通過指定某個結構體域直接賦值。

因爲 GNU C 支持 C99 標準,所以 GCC 編譯器也支持這一特性。甚至早期不支持 C99,只支持 C89 的 GCC 編譯器版本,這一特性也被當作一個 GCC 編譯器的擴展特性來提供給程序員使用。

02. 指定初始化數組元素

在 GNU C 中,通過數組元素索引,我們就可以給某個指定的元素直接賦值。

程序示例

#include <stdio.h>

int main(void)
{
    int i = 0;

    int a[10] = {[3] = 3, [5] = 5, [8] = 8};

    for (i = 0; i < 10; i++)
    {
        printf("a[%d] = %d\n", i, a[i]);
    }

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 2array.c  
deng@itcast:~/tmp$ ./a.out  
a[0] = 0
a[1] = 0
a[2] = 0
a[3] = 3
a[4] = 0
a[5] = 5
a[6] = 0
a[7] = 0
a[8] = 8
a[9] = 0

在{ }中,我們通過下表3對數組元素索引,就可以直接給 a[3] 賦值了。這裏有個細節注意一下,就是各個賦值之間用逗號 “,” 隔開,而不是使用分號“;”。

如果我們想給數組中某一個索引範圍的數組元素初始化,可以採用下面的方式。

程序示例

#include <stdio.h>

int main(void)
{
    int i = 0;

    //注意 ...之間不能有空格
    int a[10] = {[0 ... 4] = 1, [5 ... 9] = 2};

    for (i = 0; i < 10; i++)
    {
        printf("a[%d] = %d\n", i, a[i]);
    }

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 2array.c  
deng@itcast:~/tmp$ ./a.out  
a[0] = 1
a[1] = 1
a[2] = 1
a[3] = 1
a[4] = 1
a[5] = 2
a[6] = 2
a[7] = 2
a[8] = 2
a[9] = 2

在這個程序中,我們使用 [0 … 4] 表示一個索引範圍,相當於給 a[0] 到 a[4] 之間的5個數組元素賦值爲1。

GNU C 支持使用 … 表示範圍擴展,這個特性不僅可以使用在數組初始化中,也可以使用在 switch-case 語句中。比如下面的程序:

程序示例

#include <stdio.h>

int main(void)
{
    int i = 3;

    switch(i)
    {
        case 1:
            printf("1\n");
            break;
        case 2 ... 8:
            printf("2...8\n");
            break;
        case 9:
            printf("9\n");
        default:
            printf("default\n");
            break;
    }

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 3case.c  
deng@itcast:~/tmp$ ./a.out  
2...8

在這個程序中,當 case 值爲2到8時,都執行相同的 case 分支,可以通過 case 2 … 8: 的形式來簡化代碼。這

溫馨提示

… 和其兩端的數據範圍2和8之間也要空格,不能寫成2…8的形式,否則編譯就會通不過。

03. 指定初始化結構體成員變量

跟數組類似,在標準 C 中,結構體變量的初始化也要按照固定的順序。在 GNU C 中我們也可以通過結構域來初始化指定某個成員。

程序示例

#include <stdio.h>

typedef struct _stu_t
{
    int id;
    int age;
    char sex;
}stu_t;

int main(void)
{

    stu_t s1 = {1, 18, 'M'};

    stu_t s2 = {
        .id = 2,
        .age = 24,
        .sex = 'F'
    };

    printf("s1 id:%d age: %d sex: %c\n", s1.id, s1.age, s1.sex);
    printf("s2 id:%d age: %d sex: %c\n", s2.id, s2.age, s2.sex);

    return 0;
}

執行結果

deng@itcast:~/tmp$ gcc 4struct.c  
deng@itcast:~/tmp$ ./a.out  
s1 id:1 age: 18 sex: M
s2 id:2 age: 24 sex: F

在程序中,我們定義一個結構體類型 stu_t,然後分別定義兩個結構體變量 s1和 s2。初始化 s1時,我們採用標準 C 的初始化方式,即按照固定順序直接初始化。初始化 s2時,我們採用 GNU C 的初始化方式,通過結構域名 .name 和 .age,我們就可以給結構體變量的某一個指定成員直接賦值。非常方便。

04. 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 (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*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 *);
	unsigned long mmap_supported_flags;
	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 (*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 (*setfl)(struct file *, unsigned long);
	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
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

結構體 file_operations 裏面定義了很多結構體成員,而在這個驅動中,我們只初始化了部分成員變量,通過訪問結構體的成員來指定初始化,非常方便。

05. 初始化總結

指定初始化方式,不僅使用靈活,而且還有一個好處就是:代碼易於維護。尤其是在 Linux 內核這種大型項目中,幾萬個文件,幾千萬的代碼量,當成百上千個文件都使用 file_operations 這個結構體類型來定義變量並初始化時,那麼一個很大的問題就來了:如果採用標準 C 那種按照固定順序賦值,當我們的 file_operations 結構體類型發生改變時,如添加成員、減少成員、調整成員順序,那麼使用該結構體類型定義變量的大量 C 文件都需要重新調整初始化順序,牽一髮而動全身,想想這是多麼可怕!

我們通過指定初始化方式,就可以避免這個問題。無論file_operations 結構體類型如何變化,添加成員也好、減少成員也好、調整成員順序也好,都不會影響其它文件的使用。

06. 附錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章