在C坏习惯中使用灵活的数组成员?

我最近读到,在C中使用灵活的数组成员是糟糕的软件工程实践。 但是,这一说法没有任何论据支持。 这是一个公认的事实吗?

( 灵活的数组成员是C99中引入的一个C特性,可以声明最后一个元素是未指定大小的数组,例如:)

struct header { size_t len; unsigned char data[]; }; 

我不会这么做的原因是,为了使用这个function,将代码绑定到C99是不值得的。

关键是你总是可以使用下面的习惯用法:

 struct header { size_t len; unsigned char data[1]; }; 

这是完全便携的。 那么当为数组data中的n个元素分配内存时,可以考虑1:

 ptr = malloc(sizeof(struct header) + (n-1)); 

如果你已经有C99作为要求来build立你的代码,或者你是针对特定的编译器,我没有看到任何伤害。

使用goto是一个被接受的“事实”,就是软件工程实践的不足。 这并不是真的。 有时候goto是有用的,特别是在处理清理和从汇编程序移植时。

灵活的arrays成员让我觉得有一个主要用途,我的头顶,映射传统的数据格式,如RiscOS上的窗口模板格式。 在15年前,它们本来是非常有用的,我相信还有人在处理这些事情,他们会发现它们有用。

如果使用灵活的arrays成员是不好的做法,那么我build议大家都告诉C99规范的作者这个。 我怀疑他们可能有不同的答案。

你的意思是…

 struct header { size_t len; unsigned char data[]; }; 

在C中,这是一个常见的习惯用法。 我想很多编译器也接受:

  unsigned char data[0]; 

是的,这是危险的,但是再次,它实际上没有比正常的Carrays更危险 – 即非常危险;-)。 只有在真正需要大小未知的数组的情况下才使用它。 确保你的malloc正确地释放内存,使用像这样的东西: –

  foo = malloc(sizeof(header) + N * sizeof(data[0])); foo->len = N; 

另一种方法是使数据成为指向元素的指针。 然后您可以根据需要将数据重新分配到正确的大小。

  struct header { size_t len; unsigned char *data; }; 

当然,如果你问的是C ++,其中任何一个都是不好的做法。 那么你通常使用STL向量。

我见过这样的事情:从C接口和实现。

  struct header { size_t len; unsigned char *data; }; struct header *p; p = malloc(sizeof(*p) + len + 1 ); p->data = (unsigned char*) (p + 1 ); // memory after p is mine! 

注意:数据不必是最后一个成员。

作为一个方面说明,为了C89的兼容性,这样的结构应该被分配如下:

 struct header *my_header = malloc(offsetof(struct header, data) + n * sizeof my_header->data); 

或者用macros:

 #define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */ #define SIZEOF_FLEXIBLE(type, member, length) \ ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] ) struct header { size_t len; unsigned char data[FLEXIBLE_SIZE]; }; ... size_t n = 123; struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n)); 

将FLEXIBLE_SIZE设置为SIZE_MAX几乎可以确保这将失败:

 struct header *my_header = malloc(sizeof *my_header); 

关于结构如何被使用有一些缺点,如果你不考虑这些含义,这可能是危险的。

举个例子,如果你启动一个函数:

 void test(void) { struct header; char *p = &header.data[0]; ... } 

然后结果是不确定的(因为没有存储分配数据)。 这是你通常会意识到的东西,但有些情况下C程序员可能习惯于能够使用结构体的价值语义,这些结构体以各种其他方式分解。

例如,如果我定义:

 struct header2 { int len; char data[MAXLEN]; /* MAXLEN some appropriately large number */ } 

然后我可以简单地通过赋值来复制两个实例,即:

 struct header2 inst1 = inst2; 

或者,如果他们被定义为指针:

 struct header2 *inst1 = *inst2; 

然而,这是行不通的,因为可变数组data不会被复制。 你想要的是dynamicmalloc结构的大小,复制与memcpy或等效的数组。

同样,编写一个接受结构体的函数也是行不通的,因为函数调用中的参数也是通过值复制的,所以你得到的只是数组data的第一个元素。

这并不是一个坏主意,但是你必须记住,总是dynamic地分配这些结构,只能作为指针传递它们。

不,在C中使用灵活的数组成员是不错的做法。

该语言特征首先在ISO C99,6.7.2.1(16)中被标准化。 对于现行标准ISO C11,在6.7.2.1(18)中有规定。

你可以像这样使用它们:

 struct Header { size_t d; long v[]; }; typedef struct Header Header; size_t n = 123; // can dynamically change during program execution // ... Header *h = malloc(sizeof(Header) + sizeof(long[n])); h->n = n; 

或者,你可以像这样分配它:

 Header *h = malloc(sizeof *h + n * sizeof h->v[0]); 

请注意, sizeof(Header)包含最终填充字节,因此,以下分配不正确,可能会产生缓冲区溢出:

 Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid! 

一个具有灵活数组成员的结构减less了1/2的分配数量,而不是一个结构对象的2个分配,你只需要1个。因此,如果你必须分配大量的这样的结构实例,你的程序的运行时间(以一个常数)。

相比之下,对于产生未定义行为的灵活数组成员使用非标准化结构(例如在long v[0];long v[1]; )显然是不好的做法。 因此,应该避免任何未定义的行为。

自从1999年ISO C99发布以来,差不多20年前,争取ISO C89的兼容性是一个弱点。