如果分配了该区域,那么访问超出其结尾的数组是不确定的行为?

时间:2012-09-10 15:03:08

标签: c undefined-behavior

  

可能重复:
  Is the “struct hack” technically undefined behavior?

通常访问超出其结尾的数组是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;
}

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

3 个答案:

答案 0 :(得分:6)

你所描述的是被亲切地称为"the struct hack"。目前尚不清楚它是否完全没问题,但它已被广泛使用。

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

答案 1 :(得分:2)

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据说无论价值如何获得,都指向该对象。

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

答案 2 :(得分:2)

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

  

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

     

...

     

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

     

委员会认为,虽然没有办法在C89中实施“结构黑客”,但它仍然是一个有用的设施。因此,引入了“柔性阵列成员”的新特征。除了空括号,并删除malloc电话中的“-1”,   它的使用方式与struct hack相同,但现在是明确有效的代码。

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

所以答案是是,根据标准文档它是未定义的行为,但它是根据事实上的 C标准很好地定义的。我想大多数编译器编写者都非常熟悉黑客攻击。来自海湾合作委员会的tree-vrp.c

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

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