在C练习中使用灵活的阵列成员?

71 c arrays data-structures flexible-array-member

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

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

struct header {
    size_t len;
    unsigned char data[];
};
Run Code Online (Sandbox Code Playgroud)

Air*_*Ltd 25

使用goto的软件工程实践很差,这是一个公认的"事实".这并不是真的.有时goto很有用,特别是在处理清理和从汇编程序移植时.

灵活的阵列成员让我觉得有一个主要用途,在我的头顶,这是在RiscOS上映射遗留数据格式,如窗口模板格式.在大约15年前,它们对于这一点非常有用,而且我确信仍有人在那里处理那些会发现它们有用的东西.

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

  • 当我们想要在递归可能会增加编译器的额外开销的情况下使用非递归实现来实现算法的递归实现时,goto也很有用. (17认同)
  • @pranavk你可能应该使用`while`,然后. (5认同)
  • @pranavk `goto` 不是循环的对象。 (4认同)
  • 网络编程是另一种,您将标头作为结构,并将数据包(或在您所在的层中称为的包)作为灵活数组。调用下一层,剥离标头,然后传递数据包。对网络堆栈中的每个层执行此操作。(您将下层的数据从下层恢复到您所在的层的结构) (3认同)

Rem*_*o.D 19

我不赞成的原因是,为了使用这个功能,将代码绑定到C99是不值得的.

关键是你总是可以使用以下习语:

struct header {
  size_t len;
  unsigned char data[1];
};
Run Code Online (Sandbox Code Playgroud)

这是完全便携的.然后,在为数组中的n个元素分配内存时,可以考虑1 data:

ptr = malloc(sizeof(struct header) + (n-1));
Run Code Online (Sandbox Code Playgroud)

如果由于任何其他原因已经将C99作为构建代码的要求,或者您的目标是特定的编译器,我认为没有坏处.

  • "以下成语"不是完全可移植的,这就是为什么灵活的阵列成员被添加到C99标准的原因. (76认同)
  • 在C99中使用`unsigned char data [1]`是不可移植的,因为`((header*)ptr) - > data + 2` - 即使分配了足够的空间 - 创建一个指向length-1之外的指针数组对象(而不是结尾的哨兵).但是根据C99 6.5.6p8,"如果指针操作数和结果都指向同一个数组对象的元素,或者一个超过数组对象的最后一个元素,则评估不应产生溢出;**否则,行为未定义**"(强调添加).灵活的数组(6.7.2.2p16)就像一个数组填充分配的空间,不会在这里命中UB. (31认同)
  • ***警告:**使用`[1]`已导致GCC生成错误的代码:https://lkml.org/lkml/2015/2/18/407 (21认同)
  • @ Remo.D:小点:`n-1`没有准确地考虑额外的分配,因为对齐:在大多数32位机器上,sizeof(struct header)将是8(保持4的倍数,因为它有一个32位字段,它更喜欢/需要这样的对齐)."更好"的版本是:`malloc(offsetof(struct header,data)+ n)` (9认同)
  • 谢谢.我离开了n-1,因为它可能不会被用作字符串. (4认同)
  • 乔纳森的观点得到了委员会(或至少一些成员)的赞同,但它与事实相矛盾.当一起考虑时,标准**的其他几个部分需要**旧的[1]`技巧和`[]`一样工作(除了可能浪费一些额外的存储字节). (4认同)
  • [CERT安全编码标准](https://www.securecoding.cert.org/confluence/display/seccode)不建议使用此成语:"[MEMxx-C.了解如何使用灵活的阵列成员](https ://www.securecoding.cert.org/confluence/display/seccode/MEMxx-C.+Understand+how+flexible+array+members+are+to+be+used)" - "使用这种方法的问题是访问除第一个数据元素以外的行为是未定义的" (4认同)
  • @Remo.D 为什么11年后你仍然没有修改或删除这个答案?正如其他评论所指出的那样,使用“foo[1]”而不是“foo[]”不仅是错误的,因为它“不可移植”,而且是“危险的”错误,因为它会导致未定义的行为-- 使编译器默默地生成不正确/非预期的字节码。 (3认同)
  • @R ..我没有一个实际的反例,声称`[1]`必须和`[]`一样工作,但是这篇文章中的第一个例子很接近:GCC(甚至不是特别是最近的版本假设对数组成员的访问保留在数组中.由于礼貌,GCC并不认为`q-> tab [2]`是第二个例子中无法达到的表达式.http://blog.frama-c.com/index.php?post/2013/07/31/From-Pascal-strings-to-Python-tuples (2认同)
  • @supercat 这是一个非常滑坡的开始 (2认同)
  • @supercat都不赞成。在C89中,它们是在某些情况下合理的骇客。在C99中,专门引入了Flexible Array Member,以提供一个定义明确的工具,使所有这些黑客都变得不必要。旧代码应迁移。FAM具有不完整类型的优点,因此不可能意外地将`sizeof'应用于它 (2认同)
  • **警告**:当使用`gcc -mmpx -fcheck-pointer-bounds`时,使用`[1]`将导致绑定违规报告。 (2认同)

Rod*_*ddy 10

你的意思是...

struct header
{
 size_t len;
 unsigned char data[];
}; 
Run Code Online (Sandbox Code Playgroud)

在C中,这是一个常见的习语.我想很多编译器也接受:

  unsigned char data[0];
Run Code Online (Sandbox Code Playgroud)

是的,这很危险,但是再一次,它确实没有比普通C阵列更危险 - 即非常危险;-).小心使用它,并且仅在您真正需要未知大小的数组的情况下使用它.确保你使用malloc并使用以下内容正确释放内存: -

  foo = malloc(sizeof(header) + N * sizeof(data[0]));
  foo->len = N;
Run Code Online (Sandbox Code Playgroud)

另一种方法是使数据只是指向元素的指针.然后,您可以根据需要将数据realloc()重新设置为正确的大小.

  struct header
    {
     size_t len;
     unsigned char *data;
    }; 
Run Code Online (Sandbox Code Playgroud)

当然,如果你问的是C++,那么这些都是不好的做法.然后你通常使用STL向量.

  • 命名一个接受零长度数组的编译器.(如果答案是GCC,现在命名为另一个.)它不受C标准的制裁. (7认同)
  • C++但没有STL ......这不是一个愉快的想法! (6认同)
  • 我曾在C++中工作但没有STL环境 - 我们有自己的容器,它提供了常用的功能,而没有STL迭代器系统的完整通用性.它们更容易理解并且具有良好的性能.但是,这是在2001年. (3认同)
  • 只要您在支持STL的系统上编码! (2认同)

Nya*_*yan 6

我见过这样的东西:从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! 
Run Code Online (Sandbox Code Playgroud)

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

  • 实际上,这有一个优点,即`data`不一定是最后一个成员,但每次使用`data`时它也会产生额外的解引用.灵活的数组使用与主结构指针的常量偏移来替换该去引用,这在一些特别常见的机器上是免费的,而在其他地方则便宜. (17认同)

max*_*zig 6

不,在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;
Run Code Online (Sandbox Code Playgroud)

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

Header *h = malloc(sizeof *h + n * sizeof h->v[0]);
Run Code Online (Sandbox Code Playgroud)

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

Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!
Run Code Online (Sandbox Code Playgroud)

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

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

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


dia*_*pir 5

作为旁注,为了 C89 兼容性,此类结构应分配如下:

struct header *my_header
  = malloc(offsetof(struct header, data) + n * sizeof my_header->data);
Run Code Online (Sandbox Code Playgroud)

或使用宏:

#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));
Run Code Online (Sandbox Code Playgroud)

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

struct header *my_header = malloc(sizeof *my_header);
Run Code Online (Sandbox Code Playgroud)

  • 过于复杂,如果甚至需要的话,使用 `[1]` 来实现 C89 兼容性没有任何好处...... (6认同)

wds*_*wds 5

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

对于您的示例,如果您启动一个函数:

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

  ...
}
Run Code Online (Sandbox Code Playgroud)

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

例如,如果我定义:

struct header2 {
  int len;
  char data[MAXLEN]; /* MAXLEN some appropriately large number */
}
Run Code Online (Sandbox Code Playgroud)

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

struct header2 inst1 = inst2;
Run Code Online (Sandbox Code Playgroud)

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

struct header2 *inst1 = *inst2;
Run Code Online (Sandbox Code Playgroud)

然而,这不适用于灵活的数组成员,因为它们的内容不会被复制。您想要的是动态地分配结构的大小并使用memcpy或等效的方法复制数组。

struct header3 {
  int len;
  char data[]; /* flexible array member */
}
Run Code Online (Sandbox Code Playgroud)

同样,编写接受 a 的函数struct header3将不起作用,因为函数调用中的参数再次按值复制,因此您将获得的可能只是灵活数组成员的第一个元素。

 void not_good ( struct header3 ) ;
Run Code Online (Sandbox Code Playgroud)

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

 void good ( struct header3 * ) ;
Run Code Online (Sandbox Code Playgroud)