我最近读到在 C 中使用灵活的数组成员是糟糕的软件工程实践。但是,该声明没有得到任何论据的支持。这是公认的事实吗?
(Flexible array members 是 C99 中引入的一项 C 功能,可以将最后一个元素声明为未指定大小的数组。例如:)
struct header {
size_t len;
unsigned char data[];
};
使用 goto 是一种糟糕的软件工程实践,这是一个公认的“事实”。这并不成立。有时 goto 很有用,特别是在处理清理和从汇编程序移植时。
灵活的数组成员让我印象深刻,因为它有一个主要用途,在我的脑海中,它是映射遗留数据格式,如 RiscOS 上的窗口模板格式。大约 15 年前,它们在这方面会非常有用,而且我敢肯定仍然有人在处理这些事情时会发现它们很有用。
如果使用灵活的数组成员是不好的做法,那么我建议我们都去告诉 C99 规范的作者。我怀疑他们可能有不同的答案。
请仔细阅读此答案下方的评论
随着 C 标准化向前发展,没有理由再使用 [1]。
我之所以不这样做,是因为为了使用此功能而将您的代码绑定到 C99 是不值得的。
关键是您始终可以使用以下成语:
struct header {
size_t len;
unsigned char data[1];
};
那是完全便携的。然后,您可以在为数组 data
中的 n 个元素分配内存时将 1 考虑在内:
ptr = malloc(sizeof(struct header) + (n-1));
如果您出于任何其他原因已经将 C99 作为构建代码的要求,或者您的目标是特定的编译器,我认为没有什么坏处。
n-1
不能准确地考虑额外分配,因为对齐:在大多数 32 位机器上, sizeof(struct header) 将为 8(保持 4 的倍数,因为它有喜欢/需要这种对齐的 32 位字段)。 “更好”的版本是:malloc(offsetof(struct header, data) + n)
unsigned char data[1]
是不可移植的,因为 ((header*)ptr)->data + 2
- 即使分配了足够的空间 - 创建一个指向长度为 1 的数组对象之外的指针(而不是结束后的哨兵)。但是根据 C99 6.5.6p8,“如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义”(强调)。灵活数组 (6.7.2.2p16) 就像填充分配空间的数组一样,不会在此处命中 UB。
[1]
会导致 GCC 生成不正确的代码:lkml.org/lkml/2015/2/18/407
不,在 C 中使用 flexible array members 是不错的做法。
此语言功能首先在 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!
具有灵活数组成员的结构将为其分配的数量减少了 1/2,即您只需要 1 个而不是一个结构对象的 2 个分配。这意味着更少的工作量和更少的内存分配器簿记开销占用的内存。此外,您可以为一个额外的指针保存存储空间。因此,如果您必须分配大量此类结构实例,您可以显着提高程序的运行时和内存使用率(通过一个常数因子)。
与此相反,对产生未定义行为的灵活数组成员(例如在 long v[0];
或 long v[1];
中)使用非标准化构造显然是不好的做法。因此,作为任何未定义的行为,应该避免这种情况。
自从 ISO C99 于 1999 年发布,也就是 20 多年前,争取 ISO C89 兼容性是一个软弱的论据。
你的意思是...
struct header
{
size_t len;
unsigned char data[];
};
在 C 中,这是一个常见的习惯用法。我认为许多编译器也接受:
unsigned char data[0];
是的,这很危险,但话又说回来,它实际上并不比普通的 C 数组更危险 - 即,非常危险 ;-) 。小心使用它,并且仅在您真正需要未知大小的数组的情况下使用。确保你 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!
注意:数据不必是最后一个成员。
data
不必是最后一个成员,但每次使用 data
时都会产生额外的取消引用。灵活的数组用主结构指针的恒定偏移量替换了取消引用,这在一些特别常见的机器上是免费的,而在其他地方很便宜。
unsigned char *
,p->data = (unsigned char*) (p + 1 )
即可。然而,对于 double complex *
,p->data = (double complex *) (p + 1 )
可能会导致对齐问题。
附带说明一下,为了 C89 兼容性,应该像这样分配这样的结构:
struct header *my_header
= malloc(offsetof(struct header, data) + n * sizeof my_header->data);
或使用宏:
#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);
[1]
来实现 C89 兼容性没有任何好处,如果它甚至需要的话......
有时如何使用结构有一些缺点,如果您不考虑其中的含义,可能会很危险。
对于您的示例,如果您启动一个函数:
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;
但是,这不适用于灵活的数组成员,因为它们的内容不会被复制。您想要的是动态 malloc 结构的大小并使用 memcpy
或等效项复制数组。
struct header3 {
int len;
char data[]; /* flexible array member */
}
同样,编写一个接受 struct header3
的函数也不起作用,因为函数调用中的参数再次按值复制,因此您将获得的可能只是灵活数组成员的第一个元素。
void not_good ( struct header3 ) ;
使用这并不是一个坏主意,但您必须牢记始终动态分配这些结构并仅将它们作为指针传递。
void good ( struct header3 * ) ;
不定期副业成功案例分享
while
。goto
不是 for 循环。