0

这是测试代码:

#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] 内存(这是实际应用程序中的设计),不会发生内存/堆栈溢出。我不问这是否是一种好的编程风格,我只是想知道上述问题的正确答案。

更新:我接受了带有标准细节的答案,但最大的帮助是带有以下链接的评论:“struct hack”在技术上是未定义的行为吗?

4

2 回答 2

12

您的程序具有未定义的行为。这里:

t.m[1] = 123;

您正在写入一个越界位置(m是一个元素的数组,并且您正在索引一个不存在的第二个元素),对于以下情况也是如此:

t.set(1, 1);

因为它基本上最终会做:

m[1] = 1;

您不能对具有未定义行为的程序抱有任何期望——尤其是不一致的行为。

于 2013-05-03T18:13:22.187 回答
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数组重叠。你可能会覆盖编译器在那里编写的某种魔法元数据(当然,更可能是多态类型)。

于 2013-05-03T18:16:15.153 回答