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

我最近读到,在 C 语言中使用灵活的数组成员是不好的软件工程实践。然而,这种说法没有任何论据支持。这是公认的事实吗?

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

struct header {
size_t len;
unsigned char data[];
};
71374 次浏览

你是说..。

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 向量代替。

使用 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作为构建您的代码的任何其他原因的要求或您的目标是一个特定的编译器,我看不出有什么坏处。

另外,对于 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);

我见过这样的东西: 从 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!

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

有时候使用结构会带来一些负面影响,如果您不仔细考虑其中的含义,这可能会很危险。

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

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 * ) ;

不,在 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!

具有灵活数组成员的结构体可以将分配数量减少1/2,也就是说,一个结构体对象只需要1个分配数,而不是2个分配数。这意味着更少的努力和内存分配器簿记开销占用更少的内存。此外,您将存储保存为一个额外的指针。因此,如果您必须分配大量这样的结构实例,那么您就可以显著地提高程序的运行时和内存使用(通过一个常量因子)。

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

自从 ISOC99在20多年前的1999年发布以来,争取 ISOC89兼容性是一个很弱的论点。