一文详解C语言的柔性数组到底是什么?

目录

一、问题的引出

二、有哪些特点

三、实际的应用

1、取代定长数组成员

2、取代指针成员

四、优点的总结


一、问题的引出

        最近在一些程序中有时会看到结构体中成员有如下的定义场景:

//定义场景1
struct Str
{
  int a;
  ......
  int b[];
};
//定义场景2
struct Str
{  
  int a;
  ......  
  int b[0];
};

        可以看到在结构体成员中的最后一个成员被定义成了不定长度的数组b[],或者定义成了零长度数组b[0]。查阅资料得知,C99中结构体的最后一个元素允许是未知大小的数组,这就叫作柔性数组,这是一种"不完整类型",即对一个对象来说,缺乏一个完整的长度来进行描述。根据编译器类型,柔性数组可以支持b[]或者b[0]这两种不同的写法。


二、有哪些特点

  • 定义柔性数组之前需要先定义其他成员
struct Str
{  
  int a;   
  ......
  int b[]; 
};

        如果需要在结构体里面定义一个柔性数组,那么在定义柔性数组之前需要定义其他的成员变量,至少是一个非柔性数组的成员变量。

  • 结构体内存大小不包括柔性数组的内存大小
struct Str
{    
  int a;    //int类型变量大小为4个字节
  int b[];  //或定义成int b[0]
};
void main()
{
   int len = sizeof(struct Str); //len=4,不包含b[]的大小
}

        对于编译器来说,未定义长度的数组或者长度为0的数组作为结构体的"不完整类型"成员,并不会占用内存的空间,因此计算带有柔性数组成员的结构体大小时,会将柔性数组省略;对于编译器来说,数组名只是一个符号,在结构体它仅作为一个偏移量,同时也是一个不可以修改的地址常量。

  • 需要使用动态内存分配为包含柔性数组成员的结构申请内存空间
struct Str
{      
  int a;    //int类型变量大小为4个字节   
  int b[];  //或定义成int b[0] 
};
void main() 
{
  //struct Str c; //这样定义错误,c就是固定4个字节,失去柔性数组的意义   
  //sizeof(struct Str)是为a分配的大小,6*sizeof(int)是为b[]分配的大小 
  struct Str* p = (struct Str*)malloc(sizeof(struct Str) + 6*sizeof(int));
  p->a = 10; 
  for(int i = 0; i < 6; i++) 
  {
      p->b[i] = i+1; 
  }
  free(p);  
  p = NULL; 
}

        由于使用带柔性数组的结构主要的目的是为了可以构造可变长度的结构体变量,因此使用动态内存分配的方法在堆空间定义变量,分配的内存空间需要大于结构体大小,从而确定柔性数组所需要的实际大小。


三、实际的应用

        在一个结构体的最后, 定义一个柔性数组, 就可以使得这个结构体是可变长的。对于编译器来说, 此时柔性数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量。在实际的使用中也是围绕这个核心:

1、取代定长数组成员

        举个例子,使用定长数组时,如果我们在定义通信数据的数据包时,假设某个时刻数据长度为64,但是又考虑到扩展性,会将数组长度定义大一些。

        采用定长数组和柔性数组对比:

//使用定长数组
struct Str1 
{        
  int len;       //4个字节 
  char data[128];//128个字节 
};
//使用柔性数组
struct Str2 
{  
  int len;     //4个字节  
  char data[]; //0个字节 
};

void main() 
{
  //使用定长数组 
  struct Str1* p1 = (struct Str1*)malloc(sizeof(struct Str1));     
  p1->len = 64;     
  for(char i = 0; i < 64; i++)     
  {          
    p1->data[i] = i; //data数组大小为128,实际用了64,剩下的空间浪费 
  }    
  free(p1);    
  p1 = NULL; 

  //使用柔性数组
  struct Str* p2 = (struct Str2*)malloc(sizeof(struct Str2) + 64*sizeof(char)); 
  p2->len = 64;  
  for(char i = 0; i < 64; i++)     
  {      
    p2->data[i] = i;  //数据长度为64,为柔性数组申请了64字节,资源不浪费
  }  
  free(p2);  
  p2 = NULL;
}

        可以看出,使用定长数组的结构,在申请内存空间时,变量的数组成员大小为固定的大小,如果没有用到那么多数组空间,那么剩下没用到的数组空间就浪费了;使用柔性数组的结构时,需要用到多少数组空间就可以申请多少数组空间,比较灵活。

2、取代指针成员

        同样以定义通信数据的数据包举例,假设某个时刻数据长度为64。

        采用指针成员和柔性数组对比:

//使用指针成员
struct Str1 
{        
  int len;     //4个字节 
  char* data;  //指针占4个字节 
};
//使用柔性数组 
struct Str2 
{  
  int len;     //4个字节  
  char data[]; //0个字节 
};

void main() 
{
  //使用指针成员 
  struct Str1* p1 = (struct Str1*)malloc(sizeof(struct Str1)); 
  p1->len = 64; 
  p1->data = (char*)malloc(64*sizeof(char));//继续为指针成员分配空间
  for(char i = 0; i < 64; i++)    
  {
     p1->data[i] = i; 
  }
  free(p1->data); //需要先释放指针成员空间 
  p1->data = NULL; 
  free(ps); 
  p1 = NULL; 

  //使用柔性数组 
  struct Str* p2 = (struct Str2*)malloc(sizeof(struct Str2) + 64*sizeof(char)); 
  p2->len = 64;  
  for(char i = 0; i < 64; i++)     
  {      
    p2->data[i] = i;  //数据长度为64,为柔性数组申请了64字节,资源不浪费
  }  
  free(p2);  
  p2 = NULL;
}

        可以看出,使用指针成员的结构,在申请内存空间时,需要先申请定义的结构体变量空间,再申请结构体中指针成员的空间,释放时,先释放结构体中指针成员的空间,再释放结构体成员空间;使用柔性数组的结构时,就只需要对定义的结构体变量申请和释放空间。


四、优点的总结

        从以上的分析可以看出,在实际的程序中,面对某些场景在结构体中定义柔性数组成员可以有如下的优点:

        1、柔性数组不占用结构体的任何空间大小。

        2、相对在结构体中使用固定长度数组成员,柔性数组可以根据所需要使用的空间大小来申请空间,更加灵活,防止造成内存空间浪费。

        3、相对于在结构体中使用指针作为成员,定义结构体指针变量时,带柔性数组的结构体只需要进行一次动态内存申请;而带指针成员的结构体需要先为定义的结构体变量进行一次动态内存申请,再对结构体变量中的指针成员进行一次动态内存申请,多次内存申请比较麻烦,也容易出错。

        4、相对于在结构体中使用指针作为成员,定义结构体指针变量时,带柔性数组的结构体只要一次动态内存申请,该内存是连续的;而带指针成员的结构体需要多次申请内存,内存是不连续的(多次malloc的内存不一定就是连着一块的),会导致内存碎片。

        5、接第4条,连续的内存有益于提高访问速度,因为CPU在访问时,遵循局部连续性访问原理,因此将数据连续存储,可以提高访问速度。

        6、相对于在结构体中使用指针作为成员,定义的结构体指针变量在进行内存释放时,带柔性数组的结构体只需要释放一次;而带指针成员的结构体需要先释放变量的指针成员,再释放变量,如果释放顺序不对或者漏释放,容易造成内存泄露。


↓↓↓更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”↓↓↓ 

猜你喜欢

转载自blog.csdn.net/helloqusheng/article/details/131056710