Linux C中结构体初始化(C99标准)

在阅读GNU/Linux内核代码时,我们会遇到一种特殊的结构初始化方式。该方式是某些C教材(如谭二版、K&R二版)中没有介绍过的。这种方式称为指定初始化(designated initializer)。下面我们看一个例子,Linux-2.6.x/drivers/usb/storage/usb.c中有这样一个结构体初始化项目: 
static struct  usb_driver usb_storage_driver = { 
.owner  = THIS_MODULE, 
.name  = "usb-storage", 
.probe  = storage_probe, 
.disconnect  = storage_disconnect, 
.id_table  = storage_usb_ids, 
}; 
乍一看,这与我们之前学过的结构体初始化差距甚远。其实这就是前面所说的指定初始化在Linux设备驱动程序中的一个应用,它源自ISO C99标准。以下我摘录了C Primer Plus第五版中相关章节的内容,从而就可以很好的理解2.6版内核采用这种方式的优势就在于由此初始化不必严格按照定义时的顺序。这带来了极大的灵活性,其更大的益处还有待大家在开发中结合自身的应用慢慢体会。 
已知一个结构,定义如下 
struct  book { 
char   title [MAXTITL]; 
char  author [MAXAUTL]; 
float  value
}; 
C99支持结构的指定初始化项目,其语法与数组的指定初始化项目近似。只是,结构的指定初始化项目使用点运算符和成员名(而不是方括号和索引值)来标识具体的元素。例如,只初始化book结构的成员value,可以这样做: 
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赋给成员value,因为它在结构声明中紧跟在author成员之后。新的值0.25代替了早先的赋值18.90。 
有关designated initializer的进一步信息可以参考c99标准的6.7.8节Ininialization。
这篇转载大致解决了我们疑惑的问题。其实很简单,绕来绕去的,不就是初始化吗!
  
但是随后又在c语言扩展中找到了如下的起码是我不知道的事情。
   标准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会舍弃它们并产生一个警告
<think>好的,我现在需要解决用户的问题:如何在C语言中正确初始化嵌套结构体,避免顺序混乱。用户还提到需要解决方案和示例,并且希望了解如何避免乱序的问题。 首先,我得回忆一下C语言中结构体初始化的基本方法。结构体初始化通常可以在定义时直接赋值,或者在后续通过成员访问来赋值。不过,对于嵌套结构体,情况会更复杂一些,因为涉及到多层结构体的成员。 用户提到的“乱序”可能是指在初始化时,如果按照默认的顺序进行赋值,可能会因为结构体成员顺序的变化而导致错误。例如,如果结构体的定义后来被修改了,成员的顺序调整了,而初始化时还是按照原来的顺序赋值,就会出错。这种情况下,如何确保初始化不受结构体成员顺序的影响呢? 根据引用中的资料,特别是引用[2]和引用[3],GNU C提供了指定初始化器(Designated Initializers),允许通过成员名称来初始化结构体,而不必依赖顺序。这在Linux内核驱动中很常见,比如初始化file_operations结构体时,直接指定.open、.read等成员的值。这种方法可以避免顺序问题,因为每个成员都是显式指定的。 接下来,我需要考虑如何将这些知识应用到嵌套结构体初始化中。例如,如果有一个外层结构体包含另一个内层结构体,如何在初始化时指定内层结构体的成员? 引用[3]中的示例展示了嵌套结构体初始化,其中YYY_Info结构体包含三个XXX_Info类型的成员。在初始化时,用户使用了嵌套的大括号,按照顺序分别初始化Y1、Y2、Y3。但这种方法依赖于顺序,如果结构体的成员顺序变化,初始化就会出错。所以,用户的问题是如何在这种情况下避免顺序混乱,而解决方案可能就是使用指定初始化器,同时应用到嵌套结构体中。 另外,引用[4]提到结构体内存对齐的问题,虽然用户的问题是关于初始化的,但可能需要注意到结构体成员的内存布局可能影响其大小,不过这可能不是当前问题的重点,暂时可以忽略。 接下来,我需要整理出具体的解决方案和示例。首先,使用指定初始化器对外层和内层结构体的成员进行按名称初始化,这样可以不依赖于定义的顺序。然后,针对不同的C标准(如C99及以上支持指定初始化器),给出相应的示例。 可能的步骤: 1. 使用GNU C的指定初始化器,按成员名称初始化外层结构体的成员。 2. 对于嵌套的结构体成员,同样使用指定初始化器,指定内层结构体的成员名称。 3. 提供示例代码,展示如何正确初始化,包括顺序正确和乱序但正确的两种情况,以说明不依赖顺序的优势。 另外,需要注意旧版本的C标准可能不支持指定初始化器,但用户可能在使用较新的编译器或者GNU扩展,所以需要说明这一点。 现在,我需要验证这些思路是否正确。例如,在C99或更高版本中,是否可以对外层和内层结构体的成员同时使用指定初始化器。例如: struct Inner { int a; int b; }; struct Outer { struct Inner inner1; struct Inner inner2; }; struct Outer obj = { .inner2 = { .b = 5, .a = 3 }, .inner1 = { .a = 1, .b = 2 } }; 这样的初始化是否合法?是的,根据C99标准,可以这样指定。这样,无论结构体成员的顺序如何变化,只要名称正确,初始化就不会出错。 因此,解决方案应该是使用指定初始化器,按成员名称初始化,无论是外层还是内层结构体。 现在,需要将这个过程用中文清晰地表达出来,给出示例代码,并引用相关资料中的内容作为支持。例如,引用[2]中的Linux内核驱动示例,说明指定初始化器的实际应用,引用[3]中的嵌套结构体初始化示例,但指出其依赖顺序的问题,进而展示如何用指定初始化器改进。 最后,生成相关问题,可能涉及结构体初始化的其他方法、指定初始化器的兼容性问题、结构体内存对齐对初始化的影响等。</think>### 嵌套结构体初始化解决方案 在C语言中,**嵌套结构体初始化**可通过以下两种方式避免顺序混乱: --- #### 方法一:**顺序依赖初始化**(易错) 直接按照结构体成员定义的顺序逐层初始化,但若结构体定义顺序改变则会导致错误: ```c typedef struct { int x; int y; } Point; typedef struct { Point start; Point end; } Line; Line l1 = { {1, 2}, // 初始化start {3, 4} // 初始化end }; ``` 此方法完全依赖结构体成员的定义顺序[^3]。 --- #### 方法二:**指定初始化器(C99+)**(推荐) 通过`成员名: 值`显式指定初始化目标,完全消除顺序依赖: ```c Line l2 = { .end = {.y = 40, .x = 30}, // 内层结构体乱序初始化 .start = {.x = 10, .y = 20} }; ``` - **优势**:允许任意顺序初始化,成员名与值直接绑定 - **应用场景**:Linux内核驱动中广泛使用(如`struct file_operations`)[^2] --- #### 错误场景示例 若未使用指定初始化器且结构体定义顺序调整: ```c // 修改后的结构体定义 typedef struct { Point end; // 原定义中的start和end交换了位置 Point start; } Line; // 原初始化代码会错误地将{1,2}赋值给end Line l_error = {{1, 2}, {3, 4}}; ``` --- #### 扩展:**复合字面量初始化** 结合指定初始化器动态赋值: ```c Line l3; l3 = (Line){ .start = {.x = 5, .y = 6}, .end = {.y = 8, .x = 7} }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值