声明(五)
1.3、弹性数组成员
具有多个命名成员的结构的最后一个成员可以是不完整数组类型,这称为弹性数组成员(flexible array member)。
struct s1{ int i; double arr[]; //弹性数组成员。 }; // struct s2是合法的结构类型, // 因为匿名结构成员认为是包含匿名结构的结构的成员。 struct s2{ struct { int i; }; double arr[]; //弹性数组成员。 };
大多数情况下弹性数组成员会被忽略,特别是结构大小与省略弹性数组成员的情况相同;但与省略弹性数组成员相比,结构尾部可能存在填充。
// struct s1结构类型占8个字节, // 其中i占4个字节,尾部填充占4个字节。 struct s1{ int i; double arr[]; //弹性数组成员。 }; // struct s2结构类型占4个字节。 struct s2{ int i; };
当.运算符的左操作数是包含弹性数组成员的结构或者->运算符的左操作数是指向包含弹性数组成员结构的指针时,并且右操作数是弹性数组成员,其行为就像用一个最长的数组(与弹性数组成员具有相同的元素类型。)替换了弹性数组成员,但不会使结构大于正在访问对象;数组的偏移量与弹性数组成员相同;如果数组没有元素,其行为就像存在一个元素一样;但如果试图访问该元素或者生成一个指向该元素后那个元素的指针,其行为是未定义的。
struct s1{ int i; double arr[]; }; struct s2{ int i; char c; char arr[]; }; struct s1 s11 = {0}; struct s1 s12 = {5, {3.14}}; //非法。 s11.i = 3; s11.arr[0] = 1.23; //可能是未定义行为。 struct s2 s21; s21.arr[0] = 'A'; //合法。
s12的初始化是非法的,因为弹性数组成员是被忽略的。s11.arr[0]的赋值只有在sizeof(struct s1) >= offsetof(struct s1, arr) + sizeof(double)的情况下才是合法的。s21.arr[0]的赋值是合法的,因为sizeof(struct s2) >= offsetof(struct s2, arr) + sizeof(char)。
对于struct s1 s12 = {5, {3.14}};类似语句,一些实现(例如:GCC)只要求静态初始化(即编译时初始化。),非静态初始化是禁止的。
struct s{ int i; double arr[]; }; struct s s1 = {5, {3.14}}; //GCC编译正常。 int main(void) { struct s s2 = {5, {3.14}}; //GCC编译报错。 // 其它代码。 return 0; }
包含弹性数组成员的结构类型的一个重要用途是声明该类型指针,指向动态内存管理函数分配的内存,实现结构成员是变长数组的效果。
struct s{ int i; double arr[]; }; int size = 5; struct s *ptr = malloc(sizeof(struct s) + sizeof(double [size]));
如果malloc函数调用成功,指针ptr指向的对象在大多数情况下表现得像ptr声明为struct {int i; double arr[size];} *ptr一样。某些情况下这种等效性不成立,特别是结构成员arr的偏移量可能不同。
对于以下代码
struct s{ int i; double arr[]; }; struct s *p1 = malloc(sizeof(struct s) + sizeof(double)*5); struct s *p2 = malloc(sizeof(struct s) + sizeof(double)*5 - 2);
如果malloc函数调用成功,指针p1、p2大多数情况下表现得像进行了以下声明。
struct {int i; double arr[5];} *p1; struct {int i; double arr[4];} *p2;
对于以下代码
struct s{ int i; double arr[]; }; struct s *p1 = malloc(sizeof(struct s) + sizeof(double) + 2); struct s *p2 = malloc(sizeof(struct s) + sizeof(double) - 2); double *ptr; ptr = &(p1->arr[0]); //合法。 *ptr = 3.14; //合法。 ptr = &(p2->arr[0]); //合法。 *ptr = 1.23; //未定义行为。
指针p2指向结构的数组中没有元素,其行为就像存在一个元素一样;但如果试图给该元素赋值,其行为是未定义的。如果malloc函数调用成功,指针p1、p2大多数情况下表现得像进行了以下声明。
struct {int i; double arr[1];} *p1; struct {int i; double arr[1];} *p2;
对于赋值表达式*p1 = *p2,仅复制成员i的值;如果任何数组元素在结构前sizeof(struct s)个字节内,将被设置为不确定表示形式,可能与源数组元素表示形式不一致。