【问题标题】:gcc optimization bug or C++ rule broken?gcc 优化错误或 C++ 规则被破坏?
【发布时间】:2013-05-03 18:11:42
【问题描述】:

这是测试代码:

#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?

【问题讨论】:

  • 作为测试,如果你写void set(int x, int y) { x[(int*)m] = y; },那么麻烦的优化就停止了吗?我怀疑这会将其从“数组对象访问”更改为“取消引用有效但不安全派生的指针值”。
  • 不,只有当我这样做时(volatile int*)。

标签: c++ optimization gcc g++


【解决方案1】:

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

t.m[1] = 123;

您正在写入越界位置(m 是一个元素的数组,并且您正在索引一个不存在的第二个元素),同样适用于:

t.set(1, 1);

因为它基本上结束了:

m[1] = 1;

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

【讨论】:

  • @queen3:如果m 有一个元素,则无法访问m[1]。数组索引是从零开始的,所以它应该是m[0]
  • @queen3 你可以使用索引 0。但不能使用其他索引。
  • @queen3: 可能,除非它依赖于某些特定于 MS 并记录在案的编译器行为。这绝对是标准 C++ 中的 UB。
  • @queen3:The idiom there is totally different than what you have。这也是未定义的行为,但它更常见(仍然是不好的做法!)。你不能只是复制一些其他代码并假装你可以躲在“他们做得很好”的盾牌后面。
  • 这不仅仅是一种 Windows 方法。如果您必须使用长度未知的数据定义结构,这是常见的做法。通常结构后面的数据的实际长度会在结构标题中提及。
【解决方案2】:

这是 5.7 中的相关规则:

当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型...如果指针操作数和结果都指向同一个数组对象的元素,或者一个在数组对象的最后一个元素之后,评估不应产生溢出;否则,行为未定义。

回想一下t.m[i] 等价于*(t.m+i),因此指针添加规则开始发挥作用。

显然,指针操作数t.m 和结果(t.m + 1) 并不指向同一个数组对象的成员。但是,在这种情况下,结果是“超过数组对象的最后一个元素”。所以指针是有效的,但在严格的指针安全规则下不能被取消引用。由于您试图取消引用它,因此您又回到了未定义的行为。

请注意,不能保证t.m + 1 == t.m1,因为允许编译器在基本子对象和成员之间插入填充。

还要注意,编译器需要生成对表达式reinterpret_cast&lt;int*&gt;(reinterpret_cast&lt;intptr_t&gt;(t.m) + i * sizeof (int)) 的正确位置的内存访问,除非它定义了__STDCPP_STRICT_POINTER_SAFETY__。但没有具体说明它将如何与m1 数组重叠。你可能会覆盖编译器在那里编写的某种魔法元数据(当然,更可能是多态类型)。

【讨论】:

  • “需要编译器才能生成对正确位置的内存访问” - 您能否提供更多信息、链接或类似信息?我尝试打印 get_pointer_safety()(使用#include 内存),但即使使用 -std=c++11/c++0x,g++ 也不知道这一点,而且宏也不知道。
  • @queen3:这是定义“一个实现可能放宽了指针安全性,在这种情况下,指针值的有效性不取决于它是否是安全派生的指针值。或者,实现可能具有严格的指针安全性,在这种情况下,不是安全派生的指针值的指针值是无效的指针值,除非引用的完整对象具有动态存储持续时间并且先前已被声明为可达”
  • 嗯,好吧,使用 reinterpret_cast,确保它会访问正确的元素,因为这不再是固定大小的数组访问。
猜你喜欢
  • 1970-01-01
  • 2023-03-28
  • 2014-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 2010-09-29
相关资源
最近更新 更多