当前位置: C语言 -- 基础 -- 声明

声明(五)

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函数调用成功,指针p1p2大多数情况下表现得像进行了以下声明。

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函数调用成功,指针p1p2大多数情况下表现得像进行了以下声明。

struct {int i; double arr[1];} *p1;
struct {int i; double arr[1];} *p2;

对于赋值表达式*p1 = *p2,仅复制成员i的值;如果任何数组元素在结构前sizeof(struct s)个字节内,将被设置为不确定表示形式,可能与源数组元素表示形式不一致。