如果分配了该区域,那么访问超出其结尾的数组是不确定的行为?
可能重复:
“struct hack”在技术上是不确定的行为吗?
通常访问超出其结尾的数组是C中未定义的行为。例如:
int foo[1]; foo[5] = 1; //Undefined behavior
如果我知道数组末尾之后的内存区域是用malloc还是在堆栈上分配的话,它仍然是未定义的行为吗? 这是一个例子:
#include #include typedef struct { int len; int data[1]; } MyStruct; int main(void) { MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10); foo->data[5] = 1; }
我已经看到在几个地方使用这个模式来制作一个可变长度的结构,它似乎在实践中起作用。 这是技术上未定义的行为吗?
你所描述的是被亲切地称为“ 结构黑客 ” 。 目前尚不清楚它是否完全没问题,但它已被广泛使用。
到目前为止(C99),它已经开始被“ 灵活的数组成员 ”取代,你可以在其中放置一个int data[];
字段,如果它是结构中的最后一个字段。
6.5.6以下添加剂操作符 :
语义
8 – […]如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果与原始元素的下标不同数组元素等于整数表达式。 […]如果结果指向数组对象的最后一个元素之后,则不应将其用作评估的一元
*
运算符的操作数。
如果内存由malloc
分配,则:
7.22.3内存管理function
1 – […]如果分配成功,则返回的指针被适当地对齐,以便可以将其指定给指向具有基本对齐要求的任何类型对象的指针,然后用于访问此类对象或此类对象的数组在分配的空间中(直到空间被明确释放)。 分配对象的生命周期从分配延伸到解除分配。
但是,如果没有适当的MyStruct
,这并不支持使用这种内存,因此对于MyStruct
定义的MyStruct
,只能使用对象的声明成员。 这就是添加灵活arrays成员 (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
限定符,这是有道理的。
C99基本原理文件在第6.7.2.1节中讨论了这一点。
C99的一个新特性:有一个常见的习语称为“struct hack”,用于创建包含可变大小数组的结构:
…
这种结构的有效性一直是值得怀疑的。 在对一个缺陷报告的回复中,委员会认为它是未定义的行为,因为数组
p->items
只包含一个项目,无论该空间是否存在。 建议使用替代构造:使数组大小大于最大可能情况(例如,使用int items[INT_MAX];
),但由于其他原因,此方法也未定义。委员会认为,虽然没有办法在C89中实施“结构黑客”,但它仍然是一个有用的设施。 因此,引入了“柔性arrays成员”的新特征。 除了空括号,以及在
malloc
调用中删除“-1”之外,它的使用方式与struct hack相同,但现在是明确有效的代码。
struct hack是未定义的行为,因为不仅支持C规范本身(我确信在其他答案中有引用),但委员会甚至记录了它的意见。
所以答案是肯定的,根据标准文件它是未定义的行为,但它根据事实上的 C标准很好地定义。 我想大多数编译器编写者都非常熟悉黑客。 从GCC的tree-vrp.c
:
/* Accesses after the end of arrays of size 0 (gcc extension) and 1 are likely intentional ("struct hack"). */
我认为你很有可能在编译器测试套件中找到struct hack。