2008-11-17 122 views
48

我正在重构一些旧代码,并且发现了几个包含零长度数组的结构(见下文)。警告当然会受到编译指示的压制,但是我没有通过包含这种结构的“新”结构创建(错误2233)。数组'byData'用作指针,但为什么不使用指针呢?或长度为1的数组?当然,没有评论加入让我享受这个过程...... 任何原因使用这样的事情?任何重构这些建议?零长度数组

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

NB这是C++,Windows XP中,VS 2003

+3

这是[comp.lang.c FAQ](http://www.c-faq.com/)问题2.6中描述的“struct hack”。 Dennis Ritchie称之为“C实施中的无端愚蠢”。 C99引入了一种新的语言特性,即“灵活的数组成员”,以替代struct hack。即使是微软的编译器,它缺乏C99支持,也支持灵活的阵列成员。 – 2012-09-11 18:37:11

+0

请勿将`c`标签添加到此问题中。C++规则与C规则完全不同。 – 2014-05-21 06:03:52

+0

@BenVoigt接受的答案是纯粹的C代码,所以我想你的编辑是错误的。 c hack以同样的方式适用于c和C++ – 2014-05-21 06:14:23

回答

33

是的,这是一个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的目的具有指定长度的数组。

22

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

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

+3

不幸的是,当谈到C99支持时,Visual Studio很差。 :( – 2008-11-17 07:04:50

23

不幸的是,有几个原因会导致您在结构的末尾声明零长度的数组。它基本上赋予你从API返回的可变长度结构的能力。

Raymond Chen在这个问题上做了一个很好的博客文章。我建议你看看这篇文章,因为它可能包含你想要的答案。

注意在他的文章中,它处理的是大小为1而不是为0的数组。 这种情况是因为零长度数组是最近进入标准的情况。 他的帖子应该仍然适用于您的问题。

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

编辑

注意:虽然雷蒙德的帖子中写道:0长度数组是合法的C99他们实际上还是不合法的C99。而不是0长度的阵列在这里,你应该使用长度为1的阵列

8

你的关于“为什么不使用大小为1的数组”的说法是现货。

该代码正在执行“C struct hack”错误,因为零长度数组的声明是违反约束的。这意味着编译器可以在编译时立即拒绝你的黑客攻击,并带有停止翻译的诊断消息。

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

做了“C结构黑客”(这是一个用C方言追溯到1989 ANSI C,而且可能更早兼容)是用大小为1的完全有效的阵列正确的做法:

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

此外,代替sizeof struct someData,零件的byData之前的大小,使用计算:

offsetof(struct someData, byData); 

byData与空间分配struct someData为42个字节,我们再使用:

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

请注意,即使在数组大小为零的情况下,该计算实际上也是正确的计算。你看,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。我不想要指针,因为它们只对生成消息的进程的原始地址空间有意义。软件的内核部分使用其自己的映射在不同地址的内存视图,因此所有内容都基于偏移量计算。

0

这是值得指出IMO做尺寸计算的最佳方法,在上面链接的雷蒙德陈文章中使用。

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

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

第一个条目偏离期望分配结束的位置也是所需分配的大小。海事组织这是一个非常优雅的方式来做尺寸计算。不重要的是可变大小数组的元素类型是什么。 offsetof(或者Windows中的FIELD_OFFSET或UFIELD_OFFSET)总是以相同的方式写入。没有sizeof()表达式意外地搞乱了。