这是测试代码:
#include <stdio.h>
struct test
{
int m[1];
};
struct test2: public test
{
int m1[22];
void set(int x, int y) { m[x] = y; }
};
int main()
{
test2 t;
t.m[1] = 123;
t.set(0, 0);
t.set(1, 1);
printf("%d %d\n", t.m[0], t.m[1]);
return 0;
}
我编译一次没有,一次优化:
$ g++ -O0 testf.cpp
$ ./a.out
0 1
$ g++ -O2 testf.cpp
$ ./a.out
1 123
在我看来,gcc看到数组大小为m [1]并优化对它的访问,以便始终到第一个元素m [0]。问题是:它是优化错误还是,某些C ++规则被破坏,以便gcc可以做它做的事情,如果是,那么什么规则?
请注意,由于额外的m1 [22]内存(在真实应用程序中设计),不会发生内存/堆栈溢出。我不问这是不是一个好的编程风格,我只是好奇得到上面问题的正确答案。
更新:我接受了std详细信息的答案,但最大的帮助是评论,其中包含以下链接:Is the "struct hack" technically undefined behavior?
答案 0 :(得分:12)
您的程序有未定义的行为。这里:
t.m[1] = 123;
您正在写入一个越界位置(m
是一个元素的数组,并且您正在索引一个不存在的第二个元素),同样适用于:
t.set(1, 1);
因为它基本上结束了:
m[1] = 1;
对于具有未定义行为的程序,您不能指望任何事情 - 特别是不一致的行为。
答案 1 :(得分:10)
以下是5.7中的相关规则:
当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型...如果指针操作数和结果都指向同一个数组对象的元素,或者一个超过数组对象的最后一个元素,评估不应产生溢出;否则,行为未定义。
回想一下,t.m[i]
等同于*(t.m+i)
,因此指针添加规则发挥作用。
显然,指针操作数t.m
和结果(t.m + 1)
不指向同一数组对象的成员。但是,在这种情况下,结果是“一个超过数组对象的最后一个元素”。因此指针有效,但不能在严格的指针安全规则下解除引用。由于您试图取消引用它,因此您将回到未定义的行为。
请注意,不能保证t.m + 1 == t.m1
,因为允许编译器在基础子对象和成员之间插入填充。
另请注意,编译器需要生成对表达式reinterpret_cast<int*>(reinterpret_cast<intptr_t>(t.m) + i * sizeof (int))
的正确位置的内存访问,除非它定义__STDCPP_STRICT_POINTER_SAFETY__
。但是没有说明它与m1
数组的重叠方式。而且你可能会覆盖编译器在那里编写的某种神奇元数据(当然更可能是多态类型)。