对结构体

struct a {
 
int b;
 
int c;
 
}

有几种初始化方式:

struct a a1 = {
  .b = 1,
  .c = 2
 };

或者

struct a a1 = {
  b:1,
  c:2
 }

或者

struct a a1 = {1, 2};

内核喜欢用第一种,使用第一种和第二种时,成员初始化顺序可变。

 

标记化结构初始化语法

在Linux2.6内核中对结构体的定义形式发生了变化,不再支持原来的定义形式。

1  static struct tty_operations uart_ops =
2  {
3   .open  = uart_open,//串口打开
4   .close  = uart_close,//串口关闭
5   .write  = uart_write,//串口发送
6   .put_char = uart_put_char,//...
7   .flush_chars = uart_flush_chars,
8   .write_room = uart_write_room,
9   .chars_in_buffer= uart_chars_in_buffer,
10  .flush_buffer = uart_flush_buffer,
11  .ioctl  = uart_ioctl,
12  .throttle = uart_throttle,
13  .unthrottle = uart_unthrottle,
14  .send_xchar = uart_send_xchar,
15  .set_termios = uart_set_termios,
16  .stop  = uart_stop,
17  .start  = uart_start,
18  .hangup  = uart_hangup,
19  .break_ctl = uart_break_ctl,
20  .wait_until_sent= uart_wait_until_sent,
21 #ifdef CONFIG_PROC_FS
22  .read_proc = uart_read_proc, //proc入口读函数
23 #endif
24  .tiocmget = uart_tiocmget,
25  .tiocmset = uart_tiocmset,
26 };

这个声明采用了标记化结构初始化语法。这种写法是值得采用的,因为它使驱动程序在结构的定义发生变化时更具有可移植性,并且使代码更加紧凑且易读。标记化的初始化方法允许对结构成员进行重新排列。在某些场合下,将频繁被访问的成员放在相同的硬件缓存行上,将大大提高性能。

---LLD3

 

标记化结构初始化语法是ISO C99的用法

C Primer Plus第五版相关章节:

已知一个结构,定义如下:

struct book
{
    char title[MAXTITL];
    char author[MAXAUTL];
    float value;
};

C99支持结构的指定初始化项目,其语法与数组的指定初始化项目近似。只是,结构的指定初始化项目使用点运算符和成员名(而不是方括号和索引值)来标识具体的元素。例如,只初始化book结构的成员vlaue,可以这样做:

struct book surprise = { .value = 10.99 };

可以按照任意的顺序使用指定初始化项目:

struct book gift = {
 
    .value = 25.99, 
    .author = "James Broadfool", 
    .title = "Rue for the Toad"
};

正像数组一样,跟在一个指定初始化项目之后的常规初始化项目为跟在指定成员后的成员提供了初始值。另外,对特定成员的最后一次赋值是它实际获得的值。例如,考虑如下声明:

struct book gift = {
 
        .value = 18.90, 
        .author = "Philionna pestle", 
        0.25
    };

这将把值0.25赋给成员vlaue,因为它在结构声明中紧跟在author成员之后。新的值0.25代替了早先的赋值18.90。

有关designated initializer的进一步信息可以参考C99标准的6.7.8节Initialization。

 

代码举例:

#include <stdio.h> 
#include <stdlib.h> 
struct operators 
{ 
     void (*read1)(char *); 
     void (*read2)(char *); 
     void (*read3)(char *); 
     int n; 
}; 
 
void read1(char *data) 
{ 
     printf("read1: %s/n",data); 
} 
void read2(char *data) 
{ 
     printf("read2: %s/n",data); 
} 
void read3(char *data) 
{ 
     printf("read3: %s/n",data); 
} 
 
int main() 
{    //传统的初始化方法 
     //struct operators my_op = {read1, read2, read3, 100};    //所谓的标记化结构初始化语法 
     struct operators my_op = {.read2 = read2, 
                              .read1 = read1, 
                              .read3 = read3, 
                              .n = 100}; 
     my_op.read1("wangyang"); 
     my_op.read2("wangyang"); 
     my_op.read3("wangyang"); 
     return 0; 
}

重点就在于main()函数中对my_op结构体的初始化语句,使用点加变量名进行初始化。用过Python的人会马上感觉这与关键字传参是多么的相似。

那它好处在哪里呢?

我想好处有三。

首先,标记传参不用理会参数传递的顺序,正如我上面的例子里表示的那样,我是先初始化了read2,然后再初始化了read1,程序员不用记忆参数的顺序;

再者,我们可以选择性传参,在传统C语言顺序传参中,如果你只想对第三个变量进行初始化,那么你不得不给第一个,第二个参数进行初始化,而有时候一个变量并没有和合适的默认值,而使用标记初始化法,你可以相当自由地对你有把握的参数进行初始化;

还有,扩展性更好,如果你要在该结构体中增加一个字段,传统方式下,为了考虑代码修改量,你最好将新添加的字段放在这个结构体的最后面,否则你将要面对大量而且无趣的修改,你可能觉得放在哪里没什么关系,但是我们都习惯了,姓名下面是性别,性别下面是年龄,接着是兴趣爱好,最后是事迹描述,如果年龄放在了最后面,难道不别扭么?

上面的例程为什么在VC++6.0中编译不同通过呢???

在bluedrum的空间中有篇名为《C版本差异 --- 结构处理差别》的第3点中讲到:

在标准C中(C89),结构标准初始化是用{}来初始化,在C99的版本,采用了可读性更强的标记化初始化,这在Linux内核和驱动中很为常见。

其中VC++6.0只支持C89初始化,GCC支持自己标记化或自己扩展初始化。

struct name_str{
 int data;
 char name[120];
 int num;
};
/* 标记式初始化,注意顺序不同,并可缺省 */
struct name_str str ={
  .num = 100;
  .name = "hxy";
  
};

/* C89 初始化 */
struct name_str str2 =
{
 100,"Andrew Huang",-2
};

/* gcc 扩展初始化 */
struct name_str str3 =
{
  name:"bluedrum";
  data:-1
}
}

个人想编译以上代码,想下个C99编译器,在百度搜索 C99编译器,解决时间:2009-07-10 21:54回答是

“VC++ 2005 支持的是C89  而不是C99  这点可以在一次对VS2005的负责人的采访中看出来,他解释了为什么VS2005支持C89 而不支持C99  目前完全支持C99标准的编译器还不存在 支持部分C99标准的编译器也不多 做的最好的是GCC”


特定的初始化

标准C89需要初始化语句的元素以固定的顺序出现,和被初始化的数组或结构体中的元素顺序一样。

在ISO C99中,你可以按任何顺序给出这些元素,指明它们对应的数组的下表或结构体的成员名,并且GNU C也把这作为C89模式下的一个扩展。这个扩展没有在GNU C++中实现。

为了指定一个数组下标,在元素值的前面写上"[index] = "。比如:

int a[6] = { [4] = 29, [2] = 15 };

相当于:

int a[6] = { 0, 0, 15, 0, 29, 0 };

下标值必须是常量表达式,即使被初始化的数组是自动的。

一个可替代的语法是在元素前面写上".[index]",没有"=",但从GCC 2.5开始就不再被使用,但GCC仍然接受。为了把一系列的元素初始化化为相同的值,写为"[first ... ... last] = value"。这是一个GNU扩展。比如:

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

如果其中的值有副作用,这个副作用将只发生一次,而不是范围内的每次初始化一次。

注意:数组的长度是指定的最大值加一。

在结构体的初始化语法中,在元素值的前面用".fieldname = "指定要初始化的成员名。例如,给定下面的结构体:

struct point { int x, y; };

和下面的初始化:

struct point p = { .y = yvalue, .x = xvalue };

等价于:

struct point p = { xvalue, yvalue };

另一有相同含义的语法是“.fieldname:”,不过从GCC 2.5开始废除了,就像这里所示:

struct point p = { y: yvalue, x: xvalue };



"[index]"或".fieldname"就是指示符。在初始化共同体时,你也可以使用一个指示符(或不再使用的冒号语法),来指定共同体的哪个元素应该使用。比如:

union foo { int i; double d; };
union foo f = { .d = 4 };

将会使用第二个元素把4转换成一个double类型来在共同体存放。相反,把4转换成union foo类型将会把它作为整数i存入共同体,既然它是一个整数。(参考5.24节共同体类型转换)

你可以把这种命名元素的技术和连续元素的普通C初始化结合起来。每个没有指示符的初始化元素应用于数组或结构体中的下一个连续的元素。比如:

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

等价于

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

当下标是字符或者属于enum类型时,标识数组初始化语句的元素特别有用。例如:

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

你也可以在"="前面协商一系列的".fieldname"和"[index]"指示符来指定一个要初始化的嵌套的子对象;这个列表是相对于和最近的花括号对一致的子对象。比如,用上面的struct point声明:

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

如同一个成员被初始化多次,它将从最后一次初始化中取值。如果任何这样的覆盖初始化有副作用,副作用的发生与否是非指定的。目前,GCC会舍弃它们并产生一个警告。