通常访问超出其结尾的数组是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;
}
我已经看到在几个地方使用这个模式制作一个可变长度的结构,它似乎在实践中起作用。这是技术上未定义的行为吗?
答案 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。