在C语言中使用弹性数组成员是不好的做法吗?


73

我最近读到在C中使用灵活数组成员的软件工程实践不佳。但是,该声明没有任何论据支持。这是公认的事实吗?

灵活数组成员是C99中引入的C功能,通过该功能,可以将最后一个元素声明为未指定大小的数组。例如:)

Answers:


27

使用goto是不良的软件工程实践是公认的“事实”。事实并非如此。有时候,goto很有用,特别是在处理清理操作和从汇编程序移植时。

灵活的数组成员的主要用途是击倒我的头,它的主要用途是在RiscOS上映射诸如窗口模板格式的旧数据格式。在大约15年前,它们在此方面将是极其有用的,而且我敢肯定,仍然有人在处理此类事情,他们会认为它们很有用。

如果使用灵活数组成员不是一个好习惯,那么我建议大家告诉C99规范的作者。我怀疑他们可能会有不同的答案。


18
当我们想使用非递归实现来实现算法的递归实现时,在递归可能会增加编译器额外开销的情况下,goto也很有用。
pranavk 2012年

7
@pranavk然后,您可能应该使用while
yyny

6
网络编程是另一种,您将标头作为结构,并将数据包(或在您所在的层中称为的包)作为灵活数组。调用下一层,剥离标题,然后传递数据包。对网络堆栈中的每个层执行此操作。(您将下层的数据从下层恢复到您所在的层的结构中)
fhtuft

1
@pranavkgoto不是for循环。
КонстантинВан

17

请仔细阅读以下答案的评论

随着C标准化的发展,没有理由再使用[1]了。

我之所以不这样做,是因为仅使用此功能将代码绑定到C99是不值得的。

关键是您可以始终使用以下惯用法:

那是完全可移植的。然后,当为数组中的n个元素分配内存时,可以考虑1 data

如果由于其他任何原因已经有C99作为构建代码的要求,或者您以特定的编译器为目标,那么我认为这没有什么害处。


4
谢谢。我离开了n-1,因为它可能不会用作字符串。
Remo.D

83
“以下习语”不是完全可移植的,这就是为什么将灵活的数组成员添加到C99标准的原因。
乔纳森·勒夫勒

9
@ Remo.D:次要点:n-1由于对齐原因,它不能准确地说明额外的分配:在大多数32位计算机上,sizeof(struct标头)将为8(保持4的倍数,因为它具有32-偏好/需要此类对齐的位字段)。“更好”的版本是:malloc(offsetof(struct header, data) + n)
汤姆·利克

32
在C99中,使用unsigned char data[1]不是可移植的,因为((header*)ptr)->data + 2-即使分配了足够的空间-也会创建一个指向length-1数组对象外部的指针(而不是指向末尾的哨兵)。但是,按照C99 6.5.6p8,“如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素之后,则评估不会产生溢出;否则,行为是不确定的”(添加了重点)。灵活数组(6.7.2.2p16)就像一个数组,填充分配的空间,不会在此处命中UB。
Jeff Walden 2013年

23
*警告:使用[1]已被证明是导致GCC产生不正确的代码:lkml.org/lkml/2015/2/18/407
乔纳森·莱因哈特

10

你的意思是...

在C语言中,这是一个常见的习惯用法。我认为许多编译器也接受:

是的,这很危险,但是再说一次,它实际上并不比普通的C数组更危险-即,非常危险;-)。请谨慎使用它,并且仅在确实需要未知大小数组的情况下使用。确保使用以下方法正确分配和释放内存:

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

当然,如果您要问有关C ++的问题,那么这两种方法都是不好的做法。然后,您通常会改用STL向量。


2
只要您在支持STL的系统上进行编码!
Airsource Ltd

6
C ++但没有STL ...这不是一个令人愉快的想法!
罗迪

7
命名一个接受零长度数组的编译器。(如果答案是GCC,请再命名一个。)它不受C标准的认可。
乔纳森·勒夫勒

3
我在C ++中工作,但没有STL环境-我们有自己的容器,这些容器提供了常用功能,而没有STL迭代器系统的全部通用性。他们更容易理解,并且表现良好。但是,这是在2001
。– pjc50

@JonathanLeffler被GCC和Clang接受,涵盖了当今使用的三个主要编译器中的两个。(MSVC是另一大平台,并且只在一个平台上(确实很常见)才有意义。)
Donal Fellows

10

不,在C中使用灵活的数组成员不是一个坏习惯。

此语言功能最早是在ISO C99 6.7.2.1(16)中标准化的。对于当前标准ISO C11,在6.7.2.1(18)节中进行了规定。

您可以像这样使用它们:

另外,您可以像这样分配它:

请注意,sizeof(Header)其中包括最终的填充字节,因此,以下分配是错误的,并且可能会导致缓冲区溢出:

具有灵活数组成员的结构将其分配数量减少1/2,即,您只需要1就可以为一个结构对象分配2个分配,这意味着更少的工作量和更少的内存分配器簿记开销占用的内存。此外,您还为另一个指针保存了存储空间。因此,如果必须分配大量这样的结构实例,则可以通过一定的因素来显着提高程序的运行时和内存使用率。

与此相反,对产生不确定行为(例如inlong v[0];long v[1];)的灵活数组成员使用非标准化构造显然是不好的做法。因此,应避免任何不确定的行为。

自从将ISO C99于1999年发布以来(即20年前),争取ISO C89兼容性一直是一个薄弱的论点。


6

我已经看过类似的东西:从C接口和实现。

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


17
实际上,这样做的好处是data不必成为最后一个成员,但是每次data使用时都会产生额外的取消引用。灵活的数组用与主struct指针的恒定偏移量代替了取消引用,该偏移量在某些特别普通的计算机上免费,而在其他地方便宜。
R .. GitHub停止帮助ICE,2010年

@R ..尽管考虑到目标地址一定是指针之后的字节,但是可以保证大约100%已位于L1高速缓存中,从而为整个取消引用提供了大约半个周期的开销。但是,这里要指出的是,灵活数组在这里是一个更好的主意。
pmttavara

unsigned char *p->data = (unsigned char*) (p + 1 )可以。但是double complex *,使用p->data = (double complex *) (p + 1 )可能会导致对齐问题。
chux-恢复莫妮卡

这个答案在技术上是不相关的,因为它做了不同的事情(它在内存中以不同的方式布置数据)。尽管它描述的模式通常很有用,但这并不意味着它可以替代其他模式。
Donal Fellows

4

附带说明一下,为了实现C89兼容性,应按以下方式分配此类结构:

或搭配巨集:

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


4
过于复杂[1],如果需要的话,使用C89兼容性也无济于事……
R .. GitHub停止帮助ICE 2010年

4

有时如何使用结构有一些缺点,如果您不仔细考虑其含义,可能会很危险。

例如,如果您启动一个函数:

然后结果是不确定的(因为没有为数据分配存储空间)。您通常会意识到这一点,但是在某些情况下,C程序员可能习惯于将值语义用于结构,这会以各种其他方式分解。

例如,如果我定义:

然后,我可以简单地通过分配复制两个实例,即:

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

但是,这将不起作用,因为data未复制变量数组。您想要的是动态malloc结构的大小,并使用memcpy或等效形式复制整个数组。

同样,编写一个接受结构的函数将不起作用,因为函数调用中的参数又是按值复制的,因此,您得到的可能只是数组的第一个元素data

这并不是一个坏主意,但是您必须牢记始终动态分配这些结构,并仅将它们作为指针传递。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.