当前位置: C语言 -- 专题 -- 弹性数组成员

弹性数组成员

一、弹性数组

根据数组下标运算符[ ]有无赋值表达式以及赋值表达式是否是整数常量表达式,数组(以一维数组为例)可分为:弹性数组、定长数组、变长数组,具体如下所示:

运算符[ ]   有无赋值表达式    { 无赋值表达式----弹性数组,例如:arr[ ]。     有赋值表达式   是否是整数常量表达式    { 是整数常量表达式----定长数组,例如:arr[5]。       不是整数常量表达式----变长数组,例如:arr[n]。


弹性数组(flexible array)是一种不完整类型(incomplete type),以下两种情况除外:

1、弹性数组出现在函数的形参列表中,这种情况下弹性数组会转换成对应的指针类型;例如:

int func(int arr[], int size);  //等同于int func(int *, int);

2、弹性数组在声明时进行了初始化,例如:

char country[] = "China";   //country是有6个char类型元素的数组。

二、弹性数组成员

ISO/IEC 9899:2018标准第6.7.2.1 Structure and union specifiers节规定:结构不能包含不完整类型成员或者函数类型成员;但作为一个特例,具有多个命名成员结构的最后一个成员可以是具有不完整类型的数组,称为弹性数组成员(flexible array member)。

struct data {
  int number;
  double arr[];
};

以上结构类型是合法的,结构struct data包含一个弹性数组成员arr


struct data{
  struct {int number;};
  double arr[];
};

由于匿名结构的成员视为是包含结构的成员,所以上例中匿名结构成员number视为是包含结构struct data的成员,因此在struct data结构中声明弹性数组成员也是合法的。


弹性数组成员可以静态初始化(static initialization),例如:

struct data {
  int number;
  double arr[];
};

struct data s1 = {2, {3.14}};       //s1具有静态存储期限。
struct data s2 = {5, {3.14, 6.28}}; //s2具有静态存储期限。

s1s2都是全局变量,具有静态存储期限,在程序编译时初始化,即静态初始化。s1创建了只有1个元素的数组,并将其初始化为3.14s2创建了2个元素的数组,并将其分别初始化为3.146.28


struct data {
  int number;
  double arr[];
};

struct data s1 = {2, {3.14}};       //合法

int main(void)
{
  struct data s2 = {2, {3.14}};     //非法
  struct data s3 = {2};             //合法
  
  /*其他代码。*/

  return 0;
}

s1s2初始化的区别在于:s1是全局变量,具有静态存储期限,在程序编译时初始化,即静态初始化;s2是局部变量,具有自动存储期限,在程序运行时初始化,即动态初始化(dynamic initialization)。使用GCC编译时,会对s2给出"error: non-static initialization of a flexible array member"的出错信息。

其他结构成员(弹性数组成员除外)可以动态初始化,结构变量s3初始化的是s3.number


给弹性数组成员的元素赋值可能导致未定义行为(undefined behavior)。

struct data {
  int number;
  double arr[];
};

int main(void)
{
  struct data s1;
  struct data s2;
  struct data *ptr = &s2;

  s1.arr[0] = 3.14;     //可能导致未定义行为。
  ptr->arr[0] = 3.14;   //可能导致未定义行为。

  /*其他代码。*/

  return 0;
}

如果sizeof(struct data) >= offsetof(struct data, arr) + sizeof(double),这种情况下上述两个赋值可能是合法的;但无论如何,这种赋值不能出现在严格遵守ISO/IEC 9899:2018标准的代码中。


包含弹性数组成员的结构变量可以像普通的结构变量一样赋值。

struct data {
  int number;
  double arr[];
} s1 = {5, {3.14, 6.28}};

int main(void)
{
  struct data s2;

  s2 = s1;

  /*其他代码。*/

  return 0;
}

这个赋值只会给结构成员number赋值。如果弹性数组的任何元素在结构的前sizeof(struct data)个字节内,则它们可能会被复制或者简单地使用不确定值覆盖。