5

可能重复:
“struct hack”在技术上是未定义的行为吗?

通常在 C 中访问超出数组末尾的数组是未定义的行为。例如:

int foo[1];
foo[5] = 1; //Undefined behavior

如果我知道已经分配了数组末尾之后的内存区域,使用 malloc 还是在堆栈上,它仍然是未定义的行为吗?这是一个例子:

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
  int len;
  int data[1];
} MyStruct;

int main(void)
{
  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;
}

我已经看到这个模式在几个地方被用来制作一个可变长度的结构,它似乎在实践中有效。它在技术上是未定义的行为吗?

4

3 回答 3

6

您所描述的内容被亲切地称为结构黑客。目前尚不清楚它是否完全可以,但它曾经并且被广泛使用。

最近(C99),它已经开始被“灵活数组成员int data[];”取代,如果它是结构中的最后一个字段,您可以在其中放置一个字段。

于 2012-09-10T15:04:17.063 回答
3

6.5.6 加法运算符下:

语义

8 - [...] 如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始下标的差异数组元素等于整数表达式。[...]如果结果指向数组对象的最后一个元素,则不应将其用作*评估的一元运算符的操作数。

如果此时分配了内存malloc

7.22.3 内存管理功能

1 - [...] 如果分配成功,则返回的指针经过适当对齐,以便可以将其分配给指向具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组在分配的空间中(直到空间被显式释放)。已分配对象的生命周期从分配一直延伸到解除分配。

然而,这并不支持在没有适当强制转换的情况下使用此类内存,因此MyStruct如上定义的,只能使用对象的声明成员。这就是添加灵活数组成员(6.7.2.1:18) 的原因。

另请注意,附录J.2 未定义行为调用了数组访问:

1 - 在以下情况下行为未定义: [...]
- 将指针添加或减少到数组对象或仅超出数组对象和整数类型会产生不指向或仅超出数组对象的结果相同的数组对象。
— 对数组对象和整数类型的指针进行加法或减法运算会产生刚好超出数组对象的结果,并用作计算的一元运算符的操作数*
— 数组下标超出范围,即使对象显然可以使用给定的下标访问(如在a[1][7]给定声明的左值表达式中) int a[4][5])

因此,正如您所注意到的,这将是未定义的行为:

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;

但是,您可以执行以下操作:

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  ((int *) foo)[(offsetof(MyStruct, data) / sizeof(int)) + 5] = 1;

C++ 在这方面比较宽松;3.9.2 复合类型 [basic.compound]有:

3 - [...]如果一个类型的对象T位于一个地址A,那么一个类型的指针,cv T*其值为地址的指针A被称为指向该对象,而不管该值是如何获得的。

restrict考虑到 C 对指针的更积极的优化机会,例如使用限定符,这是有道理的。

于 2012-09-10T15:22:14.767 回答
2

C99 基本原理文档在第 6.7.2.1 节中讨论了这一点。

C99 的一个新特性:有一个常见的习语称为“struct hack”,用于创建包含可变大小数组的结构:

...

这种结构的有效性一直值得怀疑。在对一份缺陷报告的回复中,委员会决定这是未定义的行为,因为该数组p->items仅包含一项,而与空间是否存在无关。建议了另一种构造:使数组大小大于可能的最大情况(例如,使用int items[INT_MAX];),但由于其他原因,这种方法也未定义。

委员会认为,尽管没有办法在 C89 中实现“struct hack”,但它仍然是一个有用的工具。因此引入了“灵活数组成员”的新特性。除了空括号和malloc调用中删除“-1”之外,这与 struct hack 的使用方式相同,但现在是明确有效的代码。

struct hack 是未定义的行为,不仅支持 C 规范本身(我确信其他答案中有引用),而且委员会甚至记录了它的意见。

所以答案是肯定的,根据标准文档,它是未定义的行为,但根据事实上的C 标准,它是很好定义的。我想大多数编译器作者都非常熟悉 hack。来自 GCC 的tree-vrp.c

   /* Accesses after the end of arrays of size 0 (gcc
      extension) and 1 are likely intentional ("struct
      hack").  */

我认为您甚至很有可能在编译器测试套件中找到 struct hack。

于 2012-09-10T15:48:38.620 回答