【问题标题】:Array of zero length长度为零的数组
【发布时间】:2010-09-22 15:24:06
【问题描述】:

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

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

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

【问题讨论】:

  • 这是“struct hack”,在comp.lang.c FAQ 的问题 2.6 中进行了描述。 Dennis Ritchie 称其为“与 C 实现毫无根据的亲密关系”。 C99 引入了一种新的语言特性,即“灵活数组成员”,以取代 struct hack。甚至以缺乏 C99 支持而著称的 Microsoft 编译器也支持灵活的数组成员。

标签: c++ arrays visual-c++ flexible-array-member


【解决方案1】:

是的,这是一个 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 对象。

【讨论】:

  • 这不应该至少使用new[],这是关于C++的吗?
  • @unwind:不能为此使用 new。重点是这是一个 C-Hack,在 C++ 中不需要(因为我们有更好的方法)。此外,我很确定零长度数组在 C++ 中是非法的(至少 C++03,不确定是否在 C++11 中更新)。
  • 流行的术语是“Struct Hack”。
  • 除此之外,您的计算已关闭(在一般情况下)。根据数组中对象的类型,编译器需要强加某些对齐规则,并且将成员的大小相加可能不会产生正确的大小。相反,使用offsetof 宏让编译器计算出正确的结果。 (注意:BYTEs 不是问题,假设它们被定义为一些 char 变体。)
【解决方案2】:

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

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

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

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

编辑

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

【讨论】:

  • "之所以如此,是因为零长度数组是最近才进入标准的。" 哪些标准? C++11 仍然不允许 0 长度数组 (§8.3.4/1) 以及 C99 (§6.7.5.2/1)。
  • @ildjarn 我基本上是在模仿 Raymond 在他的博文末尾所说的话。在最近与您就另一个问题进行评论讨论之前,我不知道 0 长度数组在 C99 中仍然是非法的。我会更新答案
  • 很抱歉挑剔了这么老的答案。 :-P 我只问,因为这里链接的另一个问题是“证明”0 长度数组是合法的 C++。 :-]
  • @ildjarn NP 关于挑剔旧答案。绝对不想吐出坏数据:)
  • 你能找到任何关于使用 0 长度或 [] 数组对内存对齐的影响的参考吗?在一个项目中,一位同事发现最安全的用法是 int arr[](因为 int 可以防止任何对齐问题),但是由于我们要返回数组,所以在我们的例子中最好的是 void *arr[],这是相当加密的。
【解决方案3】:

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

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

【讨论】:

  • 遗憾的是,Visual Studio 在 C99 支持方面非常糟糕。 :(
  • 没有解决您评论的一般事实,...MS VC v9 编译器支持 arr[] 语法。
【解决方案4】:

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

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

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

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

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

此外,byData 之前部分的大小不是sizeof struct someData,而是使用以下公式计算的:

offsetof(struct someData, byData);

要在byData 中分配一个具有42 字节空间的struct someData,我们将使用:

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。我不想要那里的指针,因为它们只对生成消息的进程的原始地址空间有意义。该软件的内核部分使用自己在不同地址的映射来查看内存,因此一切都基于偏移计算。

【讨论】:

  • “与可追溯到 1989 年的 C 方言兼容” - 访问数组的第一个元素会导致未定义的行为,即使在 C89 中也是如此。 struct hack 依赖于编译器为自己“定义”这种行为。
【解决方案5】:

值得指出 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() 表达式会意外搞砸。

【讨论】:

    猜你喜欢
    • 2012-09-06
    • 2017-02-26
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多