在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的兼容性是一个弱点。