Flexible Array - 柔性数组

柔性数组成员是一种特殊的数组类型,其中具有多个命名成员的结构的最后一个元素具有不完整的数组类型;

也就是说,数组的大小未在结构中明确指定。这种“struct hack”在实践中得到了广泛使用,并得到了各种编译器的支持。因此,已经使用了各种不同的语法来声明灵活的数组成员。对于符合标准的C实现,请使用C标准保证有效的语法。

the C Standard, 6.7.2.1, paragraph 18中定义了 Flexible array members ,如下所示:

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

具有柔性数组成员的结构可用于生成具有定义行为的代码。但是,存在一些限制:

  1. 不完整的数组类型必须是结构中的最后一个元素。
  2. 不能有包含柔性数组成员的结构数组。
  3. 包含柔性数组成员的结构不能用作其他结构的成员。
  4. 除了柔性数组成员外,该结构还必须包含至少一个命名成员

不兼容的代码示例

在C标准中引入柔性数组成员之前,使用以一元数组作为最终成员的结构来实现类似的功能。这个不兼容的代码示例说明了在这种情况下如何声明struct flexArrayStruct

这个不兼容的代码示例尝试分配一个灵活的类似数组的成员,并将一个元素的数组作为最终成员。实例化结构时,将修改为malloc()计算的大小以考虑动态数组的实际大小。

#include 
  
struct flexArrayStruct {
  int num;
  int data[1];
};
 
void func(size_t array_size) {
  /* Space is allocated for the struct */
  struct flexArrayStruct *structP = (struct flexArrayStruct *)malloc(sizeof(struct flexArrayStruct) 
                                   		+ sizeof(int) * (array_size - 1));
  if (structP == NULL) {
    /* Handle malloc failure */
  }
   
  structP->num = array_size;
 
  /*
   * Access data[] as if it had been allocated
   * as data[array_size].
   */
  for (size_t i = 0; i < array_size; ++i) {
    structP->data[i] = 1;
  }
}

当访问除数据数组的第一个元素以外的任何元素时,此示例具有未定义的行为。 (请参阅C标准6.5.6。)因此,编译器可以生成在访问数据的第二个元素时不返回期望值的代码。

对于尚未实现标准C语法的编译器,此方法可能是唯一的选择。

兼容解决方案

此兼容解决方案使用灵活的数组成员来实现动态大小的结构:

#include 
  
struct flexArrayStruct{
  int num;
  int data[];
};
 
void func(size_t array_size) {
  /* Space is allocated for the struct */
  struct flexArrayStruct *structP = (struct flexArrayStruct *) malloc(sizeof(struct flexArrayStruct)
       									  + sizeof(int) * array_size);
  if (structP == NULL) {
    /* Handle malloc failure */
  }
 
  structP->num = array_size;
 
  /*
   * Access data[] as if it had been allocated
   * as data[array_size].
   */
  for (size_t i = 0; i < array_size; ++i) {
    structP->data[i] = 1;
  }
}

此兼容解决方案允许将结构视为以符合C标准的方式将其成员data []声明为data [array_size]

使用场景

这样的写法最适合制作动态buffer,因为可以这样分配空间malloc(sizeof(structXXX) + buff_len); (如果有字节对齐可能比它小),直接就把buffer的结构体和缓冲区一块分配了。用起来也非常方便,因为现在空数组其实变成了buff_len长度的数组了。

结构体中最后一个成员为[0]长度数组,常用来构成缓冲区这是个广泛使用的常见技巧。

这样的好处是:

  1. 一次分配解决问题,省了不少麻烦。为了防止内存泄露,如果是分两次分配(结构体和缓冲区),那么要是第二次malloc失败了,必须回滚释放第一个分配的结构体。这样带来了编码麻烦。

    其次,分配了第二个缓冲区以后,如果结构里面用的是指针,还要为这个指针赋值。同样,在free这个buffer的时候,用指针也要两次free。如果用空数组,所有问题一次解决

  2. 小内存的管理是非常困难的,如果用指针,这个bufferstruct部分就是小内存了,在系统内存在多了势必严重影响内存管理的性能。要是用空数组把struct和实际数据缓冲区一次分配大块问题,就没有这个问题。如此看来,用空数组既简化编码,又解决了小内存碎片问题提高了性能。


柔性数组使用的优缺点:

实验:

#include 
#include 
#include 
#include 

// 字节对齐
struct test
{
	int a = 10;			    //  4+4(补)=8,因为double型需要从8的整数倍开始
	double  b = 20.0;		//  8+8
	char c = 'a';			//  8+8+1+3(补),因为Int型需要从4的整数倍开始
	int d = 30;				//  8+8+4+4 
	char e = 'e';			//  8+8+4+4+1
	char f = 'f';			//  8+8+4+4+1+6(补)最后第二个元素地址
	char buf[];				//  地址等于最后第二个元素地址+其字节长+1
};


int main(int *argc, int **argv)
{
	char ch[] = "hello world!";
	std::cout << sizeof(ch) << std::endl;

	//struct test tst;
	//tst.buf = ch;		   // 不可指定数组类型“char []”, 表达式必须是可修改的左值

	struct test* p = (struct test*)malloc(sizeof(struct test) + sizeof(ch) + 1);
	strcpy_s(p->buf, sizeof(ch) + 1, ch);
	std::cout << "结构体大小" <<sizeof(*p) << std::endl;
	std::cout << "结构体首地址" << p << " 结构体尾地址 " << (p + 1) << std::endl;

	std::cout  << &p->a << std::endl;
	std::cout  << &p->b << std::endl;
	std::cout  << (void *) &p->c << std::endl;
	std::cout  << &p->d << std::endl;
	std::cout  << (void *) &(p->e) << std::endl;
	std::cout  << (void *) &(p->f) << std::endl;
	std::cout  << "柔性数组地址/值 " << &p->buf << " " << p->buf << std::endl;
	free(p);

	return 0;
}

Flexible Array - 柔性数组_第1张图片

优点

  • 不需要使用指针来分配内存,节约一个指针变量所占内存大小,也使内存申请方式更加便捷;
  • 分配的内存连续,管理与释放简单,只需要一次操作。

缺点

  • 零长数组是GNU C的实现,非标准,因此并不是所有的编译器都支持,有移植风险。

潜在风险

声明柔性数组成员时未使用正确的语法可能导致未定义的行为,尽管错误的语法将在大多数实现中起作用
在这里插入图片描述
在CERT web上搜索由于违反此规则而导致的漏洞。

不完整类型(incomplete type)

不完整类型(incomplete type):它缺乏足够的信息例如长度去描述一个完整的对象。

  1. 前向声明就是一种常用的不完整类型, class base; struct test;basetest只给出了声明,没有给出定义。不完整类型必须通过某种方式补充完整,才能使用它们进行实例化,否则只能用于定义指针或引用,否则只能用于指针或引用,因为此时实例化的是指针或引用本身,不是basetest对象。
  2. 一个未知长度的数组也属于不完整类型:extern int a[];。这里 extern不能去掉,因为数组的长度未知,不能作为定义出现。不完整类型的数组可以通过几种方式补充完整才能使用,大括号形式的初始化就是其中一种方式:int a[] = {10, 20};

参考文献

  • [ISO/IEC 9899:2011] :6.5.6, “Additive Operators”
  • [ISO/IEC 9899:2011] :6.7.2.1, "Structure and Union Specifiers
  • [McCluskey 2001 - Flexible Array Members and Designators in C9X]:https://wiki.sei.cmu.edu/confluence/display/c/AA.+Bibliography#AA.Bibliography-McCluskey01
  • [Robert Seacord - DCL38-C]:https://wiki.sei.cmu.edu/confluence/display/c/DCL38-C.+Use+the+correct+syntax+when+declaring+a+flexible+array+member
  • [结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法]:https://blog.csdn.net/fengbingchun/article/details/24185217

你可能感兴趣的:(C/C++)