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

时间:2008-10-29 14:21:18

标签: c arrays data-structures flexible-array-member

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

Flexible array members是C99中引入的C特性,其中可以将最后一个元素声明为未指定大小的数组。例如:)

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

7 个答案:

答案 0 :(得分:24)

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

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

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

答案 1 :(得分:19)

我拒绝这样做的原因是,为了使用此功能,将代码绑定到C99是不值得的。

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

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

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

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

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

答案 2 :(得分:10)

你的意思是......

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;

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

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

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

答案 3 :(得分: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! 

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

答案 4 :(得分:6)

不,在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,即,对于一个结构对象而不是2个分配,只需要1.意味着内存分配器簿记开销占用更少的工作量和更少的内存。此外,您还可以为一个额外的指针保存存储空间。因此,如果必须分配大量此类结构实例,则可以显着提高程序的运行时和内存使用量(通过常数因子)。

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

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

答案 5 :(得分:4)

作为旁注,对于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);

答案 6 :(得分:4)

有一些缺点与有时使用结构有关,如果你不考虑这些结果,这可能是危险的。

对于您的示例,如果您启动一个功能:

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。你想要的是动态地malloc结构的大小,并用memcpy或同等的数据复制到数组。

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

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

相关问题