54

我正在重构一些旧代码,并发现很少有包含零长度数组的结构(如下)。当然,编译指示会抑制警告,但我未能通过包含此类结构的“新”结构创建(错误 2233)。数组'byData'用作指针,但为什么不使用指针呢?还是长度为 1 的数组?当然,没有添加任何评论来让我享受这个过程......有什么理由使用这样的东西吗?在重构这些方面有什么建议吗?

struct someData
{
   int nData;
   BYTE byData[0];
}

注意它是 C++、Windows XP、VS 2003

4

5 回答 5

36

是的,这是一个 C-Hack。
创建任意长度的数组:

struct someData* mallocSomeData(int size)
{
    struct someData*  result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE));
    if (result)
    {    result->nData = size;
    }
    return result;
}

现在你有了一个带有指定长度数组的 someData 对象。

于 2008-11-17T16:53:22.210 回答
26

不幸的是,在结构的末尾声明一个长度为零的数组有几个原因。它本质上使您能够拥有从 API 返回的可变长度结构。

Raymond Chen 就该主题发表了一篇出色的博文。我建议你看看这篇文章,因为它可能包含你想要的答案。

请注意,在他的帖子中,它处理大小为 1 而不是 0的数组。之所以如此,是因为零长度数组是标准中较新的条目。 他的帖子应该仍然适用于您的问题。

http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx

编辑

注意:尽管 Raymond 的帖子说 0 长度数组在 C99 中是合法的,但实际上它们在 C99 中仍然不合法。这里应该使用长度为 1 的数组,而不是长度为 0 的数组

于 2008-11-17T06:59:34.390 回答
23

这是一个旧的 C hack,允许灵活大小的数组。

在 C99 标准中,这不是必需的,因为它支持 arr[] 语法。

于 2008-11-17T06:58:04.923 回答
11

您对“为什么不使用大小为 1 的数组”的直觉是正确的。

代码做错了“C struct hack”,因为零长度数组的声明是违反约束的。这意味着编译器可以在编译时立即拒绝您的 hack,并带有停止翻译的诊断消息。

如果我们想进行黑客攻击,我们必须偷偷溜过编译器。

进行“C struct hack”(与可追溯到 1989 年 ANSI C 并且可能更早的 C 方言兼容)的正确方法是使用大小为 1 的完全有效的数组:

struct someData
{
   int nData;
   unsigned char byData[1];
}

此外,sizeof struct someData之前零件的尺寸不是 ,而是byData使用以下公式计算的:

offsetof(struct someData, byData);

要在 中分配struct someData42 个字节的空间byData,我们将使用:

struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);

请注意,offsetof即使在数组大小为零的情况下,此计算实际上也是正确的计算。你看,sizeof整个结构可以包括填充。例如,如果我们有这样的事情:

struct hack {
  unsigned long ul;
  char c;
  char foo[0]; /* assuming our compiler accepts this nonsense */
};

struct hack由于成员的原因,很可能会填充的大小以进行对齐ul。如果unsigned long是四个字节宽,那么很可能sizeof (struct hack)是 8,而offsetof(struct hack, foo)几乎可以肯定是 5。该offsetof方法是获取数组之前结构前一部分的准确大小的方法。

所以这将是重构代码的方法:使其符合经典的、高度可移植的 struct hack。

为什么不使用指针?因为指针占用了额外的空间并且必须被初始化。

不使用指针还有其他充分的理由,即指针需要地址空间才能有意义。struct hack 是可外部化的:也就是说,在某些情况下,这种布局符合外部存储,例如文件、数据包或共享内存的区域,在这种情况下,您不需要指针,因为它们没有意义。

几年前,我在内核和用户空间之间的共享内存消息传递接口中使用了 struct hack。我不想要那里的指针,因为它们只对生成消息的进程的原始地址空间有意义。该软件的内核部分使用自己在不同地址的映射来查看内存,因此一切都基于偏移计算。

于 2014-05-21T06:39:50.837 回答
2

值得指出 IMO 进行尺寸计算的最佳方法,在上面链接的 Raymond Chen 文章中使用。

struct foo
{
    size_t count;
    int data[1];
}

size_t foo_size_from_count(size_t count)
{
    return offsetof(foo, data[count]);
}

第一个条目与所需分配结束的偏移量也是所需分配的大小。IMO 这是一种非常优雅的尺寸计算方式。可变大小数组的元素类型是什么并不重要。offsetof(或 Windows 中的 FIELD_OFFSET 或 UFIELD_OFFSET)始终以相同的方式写入。没有 sizeof() 表达式会意外搞砸。

于 2018-02-09T20:20:43.860 回答