ISO/IEC 9899:2011 条款6.7.9——初始化
6.7.9 初始化
语法
1、initializer:
assignment-expression
{ initializer-list }
{ initializer-list , }
initializer-list:
designationopt initializer
initializer-list , designationopt initializer
designation:
designation-list =
designation-list:
designator
designator-list designator
designator:
[ constant-expression ]
. identifier
约束
2、所有初始化器都不应该企图为一个对象提供一个值,该对象不在正被初始化的实体内。
3、要被初始化的实体的类型应该是一个未知大小的数组或是一个完整的对象类型,它不是一个可变长度的数组类型。
4、对于一个具有静态或线程存储周期的对象,在一个初始化器中的所有表达式应该是常量表达式或字符串字面量。
5、如果一个标识符的声明具有语句块作用域,并且该标识符具有外部或内部连接,那么该声明应该不具有对此标识符的初始化器。
6、如果一个指派符具有如下形式
[ constant-expression ]
那么当前对象(在下面定义)应该具有数组类型,并且表达式应该是一个整数常量表达式。如果数组是未知大小的,那么任一非负值都是有效的。
7、如果指派符具有如下形式
. identifier
那么当前对象(在下面定义)应该具有结构体或联合体类型,并且该标识符应该具有该类型的一个成员的名字。
语义
8、一个初始化器指定了存储在一个对象中的初始值。
9、除了显式陈述,否则,出于子条款的目的,结构体和联合体类型的匿名成员并不参与初始化。结构体对象的未命名成员甚至在初始化之后仍然具有未确定的值。
10、如果一个具有自动存储周期的对象不被显式地初始化,那么其值是不确定的。如果具有静态或线程存储周期的一个对象不被显式初始化,那么
——如果它具有指针类型,那么它被初始化为一个空指针;
——如果它具有算术类型,那么热它被初始化为(正的或负的)零;
——如果它是一个聚合类型,那么每个成员被初始化(递归地),根据以上规则,并且任何填充被初始化为零比特;
——如果它是一个联合体,那么第一个命名成员被(递归地)初始化,根据以上规则,并且填充被初始化为零比特。
11、对一个标量的初始化器应该是一个单个表达式,可选地可用圆括号括起来。对象的初始值是该表达式(在转换后)的值;对此应用与简单赋值相同的类型约束和转换,取标量类型作为其所声明类型的非限定版本。
12、此子条款的剩余部分讨论对象的初始化器,这些对象具有聚合或联合类型。
13、具有自动存储周期的一个结构体或联合体对象的初始化器应该要么是在一下描述的一个初始化器列表,要么是一单个表达式,它具有兼容的结构体或联合体类型。在后者情况下,该对象的初始值,包括匿名成员,是该表达式的值。
14、一个字符类型数组可以用一个字符串字面量或UTF-8字符串字面量来初始化,可选地可包含在圆括号中。字符串的相继字节(包含终结空字符,如果有空间或如果数组是未知大小的)初始化数组的元素。
15、具有与wchar_t、char16_t或char32_t的一个限定或非限定版本相兼容的元素类型的一个数组,可以用一个带有相应编码前缀(L,u或U)的宽字符串来初始化,可选地,包含在圆括号中。宽字符串字面量相继的宽字符(包含终结空宽字符,如果有空间,或者如果数组是未知大小的)初始化该数组元素。
16、否则,具有聚合或联合体类型的一个对象的初始化器应该是一个圆括号包围的,对其元素或命名成员的初始化器列表。
17、每个用圆括号包围的初始化器列表具有一个相关联的当前对象。当当前没有指派时,当前对象的子对象根据当前对象的类型依次被初始化:数组元素以下标递增次序,结构体成员以声明次序,一个联合体初始化第一个命名成员。[注:如果一个子聚合或含有联合体的初始化列表不以一个左圆括号开头,那么其子对象仍然跟往常一样被初始化,但是子聚合或所包含的联合体不变为当前对象:当前对象仅与圆括号包围的初始化器列表相关联。]相比之下,一个指派引发了后面的初始化器开始对指派符所描述的子对象的初始化。初始化随后继续按次序进行,在指派符所描述之后的下一个子对象开始。[注:在一个联合体对象被初始化之后,下一个对象不是该联合体的下一个成员;而是,一个包含该联合体的对象的下一个子对象。]
18、每个指派符列表以与最近的一对花括号相关联的当前对象的描述开始。在指派符列表中的每一个项(按次序)指定其当前对象的一个特定成员,并为下一个指派符(如果有的话)改变当前对象为那个成员。[注:从而,一个指派符只可以指定聚合或联合类型的一个严格的子对象。同时也要注意,每个单独的指派符列表是独立的。]结果在指派符列表末尾的当前对象是被后续初始化器初始化的子对象。
19、初始化应该以初始化器列表次序发生,每个初始化器提供给一个特定子对象重写任一先前列出的初始化器为同一子对象;[注:任一子对象的初始化器,它被重写并且不被用于初始化那个子对象,可以完全不被计算。]所有不被显式初始化的子对象应该被隐式初始化为与具有静态存储周期的相同的对象。
20、如果聚合或联合体包含了聚合或联合体的元素或成员,那么这些规则递归地应用于子聚合或被包含的联合体。如果一个子聚合或被包含的联合体的初始化器以一个左花括号开头,那么由该花括号所包围的初始化器以及其配对的右花括号初始化了聚合或被包含联合体的元素或成员。否则,只有来自列表足够的初始化器用来获取,负责子聚合或被包含的联合体的元素或成员;任何剩余的初始化器被留着初始化接下去的元素或聚合成员,它们是当前子聚合或被包含的联合体的一部分。
21、如果在一个大括号包围的列表中,比起一个聚合的元素或成员具有更少的初始化器,或者在一个用于初始化一个已知大小的数组的字符串字面量中,比起在一个数组中的元素具有更少的字符,那么聚合的剩余部分应该被隐式地初始化为与具有静态存储周期的对象相同的对象。
22、如果一个未知大小的数组被初始化,那么其大小由带有一个显式初始化器的最大索引元素确定。数组类型在其初始化列表末尾是完整的。
23、初始化列表表达式们的计算,对于其中各个表达式而言是不确定次序的,并从而任何副作用发生的次序是未指定的。[注:特别地,计算次序不需要与子对象初始化次序相同。]
24、例1 以下代码已经包含了<complex.h>,该声明
int i = 3.5; double complex c = 5 + 3 * I;
定义并用值3初始化了i,用值5.0 + i3.0初始化了c。
25、例2 声明
int x[] = { 1, 3, 5 };
对x定义并初始化为一个一维数组对象,它具有三个元素。在声明时没有指定大小,但有三个初始化器。
26、例3 声明
int y[4][3] = { { 1, 3, 5 }, { 2, 4, 6 }, { 3, 5, 7 } };
是一个具有花括号充分括起来的初始化的定义:1、3和5初始化了y的第一行(数组对象y[0]),名义上为y[0][0]、y[0][1]和y[0][2]。类似的,下两行初始化了y[1]和y[2]。初始化器早早结束,因此y[3]以零来初始化。以下声明具有完全相同的效果:
int y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };
对y[0]的初始化器并不以左花括号开始,因此列表中用了三个项。类似的,下三个项相继为y[1]和y[2]进行初始化。
27、以下声明
int z[4][3] = { { 1 }, { 2 }, { 3 }, { 4 } };
初始化了z的第一列作为指定的元素值,并且其余部分用零来初始化。
28、例5 以下声明
struct { int a[3], b; } w[] = { {1}, 2 };
是一个具有不一致花括号的初始化的定义。它定义了含有两个元素结构体的数组:w[0].a[0]为1,w[1].a[0]为2;其余元素为零。
29、例6 以下声明
short q[4][3][2] = { { 1 }, { 2, 3 }, { 4, 5, 6 } };
包含了一个不完整但一致花括号的初始化。它定义了一个三维数组对象:q[0][0][0]是1,q[1][0][0]是2,q[1][0][1]是3,q[2][0][0]是4,q[2][0][1]是5,q[2][1][0]是6。其余元素都是零。q[0][0]的初始化器并不以一个左花括号开头,因此当前列表多达6个项可被使用。由于只有一个指定元素值,所以剩余5个均以零来初始化。类似的,q[1][0]和q[2][0]并不以一个左花括号开头,因此具有多达6个项可用,初始化它们相应二维子集。如果在这些列表中有任意一个多于6个项,那么应该发布一条诊断信息。以下声明可以达成完全相同的初始化结果:
short q[4][3][2] = { 1, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0, 4, 5, 6 };
或者是:
short q[4][3][2] = { { { 1 } }, { { 2, 3 } }, { { 4, 5 }, { 6 } } };
它在一个完整花括号形式。
30、注意,对于完整花括号与最小花括号形式的初始化,一般不太可能引起困惑。
31、例7 完成数组类型的初始化的一种形式涉及到typedef名。给定以下声明
typedef int A[]; // OK,在语句块作用域中声明
以下声明
A a = { 1, 2 }, b = { 3, 4, 5 };
等价于
int a[] = { 1, 2 }, b[] = { 3, 4, 5 };
出于对不完整类型的规则。
32、例8 以下声明
char s[] = "abc", t[3] = "abc";
定义了“朴素的”char数组对象s和t,其元素用字符串字面量来初始化。该声明等价于
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
数组的内容是可修改的。另一方面,以下声明
char *p = "abc";
定义了具有“指向char的指针”类型的p,并用指向一个类型为“char的数组”的对象对它初始化,长度为4个字节,其元素用一个字符串字面量来初始化。如果企图用来p修改数组的内容,那么行为是未定义的。
33、例9 数组可以被初始化为相应的一个枚举的元素,通过使用指派符:
enum { member_one, member_two }; const char *nm[] = { [member_two] = "member two", [member_one] = "member one" };
34、例10 结构体成员可以被初始化为非零值,而不依赖它们的次序:
div_t answer = { .quot = 2, .rem = -1 };
35、例11 指派符可以用于提供显式的初始化,当没有修饰的初始化器列表可能会被误解时:
struct { int a[3], b; } w[] = { [0].a = {1}, [1].a[0] = 2 };
36、例12 空格可以从一个数组的两端来被“分配”,通过使用单个指派符:
int a[MAX] = { 1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0 };
37、对于上述例子,如果MAX大于10,那么在中间会有某些零值元素;如果它小于10,那么由头5个初始化器所提供的某个值被后5个初始化器中的某一个覆盖。
38、例13 一个联合体的任一成员可以被如下初始化:
union { /* ... */ } u = { .any_member = 42 };
(结束)