编译器指令重新排序
Posted
技术标签:
【中文标题】编译器指令重新排序【英文标题】:Compiler instruction reordering 【发布时间】:2016-06-29 03:00:52 【问题描述】:struct Vec
double x, y, z;
;
Vec vec;
vec.x = 1.0;
vec.y = 2.0;
vec.z = 3.0;
double res = (&vec.x)[2]; // (&vec.x)[2] should be equal to vec.z
变量 res 的值应该等于 3。但是当我打开优化时,编译器会错误地重新排序指令并且 res 包含一些垃圾。一些可能的重新排序示例:
vec.x = 1.0;
vec.y = 2.0;
vec.z = 3.0;
res = (&vec.x)[2]; // correct value
vec.x = 1.0;
vec.y = 2.0;
res = (&vec.x)[2]; // incorrect value
vec.z = 3.0;
vec.x = 1.0;
res = (&vec.x)[2]; // incorrect value
vec.y = 2.0;
vec.z = 3.0;
这是编译器中的错误吗?还是不允许这样访问struct数据成员?
编辑:
我刚刚意识到前面的代码确实有效,对此感到抱歉。但这不起作用:
Vec vec;
vec.x = 1.0;
vec.y = 2.0;
vec.z = 3.0;
double res = (&vec.x)[i]; // (&vec.x)[i] should be equal to vec.z when i == 2
当编译时不知道变量i时,编译器会错误地重新排序指令。
【问题讨论】:
@BatCoder:如果是未定义的行为,是否可以重现并不重要。 Javers,(&vec.x)[2]
是什么意思,你怎么理解这个?
(&vec.x)[2] 获取 vec.x 的地址,添加 2 并取消引用它。它应该返回 vec.z 的值
如果您想要类似数组的行为,您应该重载结构的下标运算符(即 operator[]),而不是尝试以无法合法使用的方式使用指针运算。见:learncpp.com/cpp-tutorial/98-overloading-the-subscript-operator
原始代码使用 operator[] 做同样的事情——它返回 (&x)[i]
【参考方案1】:
您正在调用未定义的行为。 (&vec.x)[2]
等同于 (&vec.x) + 2
,标准 (§[expr.add]/4) 对指针添加有以下说法(强调我的):
当具有整数类型的表达式被添加或减去时 从一个指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象84的一个元素,而 数组足够大,结果指向一个元素偏移量 原始元素使得下标的差异 结果和原始数组元素等于积分表达式。 换句话说,如果表达式 P 指向一个 数组对象,表达式 (P)+N(等价于 N+(P))和 (P)-N (其中 N 的值为 n)分别指向 i + n-th 和 i - 数组对象的第 n 个元素,前提是它们存在。此外,如果 表达式 P 指向数组对象的最后一个元素, 表达式 (P)+1 指向数组对象的最后一个元素, 如果表达式 Q 指向数组的最后一个元素 对象,表达式 (Q)-1 指向数组的最后一个元素 目的。 如果指针操作数和结果都指向元素 相同的数组对象,或数组的最后一个元素 对象,评估不应产生溢出;否则, 行为未定义。
另见上文引用的注释 84:
84) 为此目的,一个不是数组元素的对象被认为属于一个单元素数组;见 5.3.1。
由于vec.x
不是数组,所以被认为是单元素数组的一个元素。因此vec.x
和vec.z
不是同一个数组的元素,行为未定义。
【讨论】:
【参考方案2】:除了未定义的行为,请注意 C++ 不会强制将它们分配在一起。如果您想同时通过索引和名称来引用它们,我建议您采用另一种方式:
struct Vec
double elems[3];
double& x;
double& y;
double& z;
Vec()
: x( elems[0] ), y( elems[1] ), z( elems[2] )
;
如果您负担不起每个对象可能(但不是必需的)额外空间,您不妨将 x、y、z 声明为返回引用的函数。
【讨论】:
【参考方案3】:我最终找到了一个解决方案,它可以防止指令重新排序并且不会增加 Vec 结构的大小:
struct Vec
union
struct
double x, y, z;
;
double data[3];
;
;
Vec vec;
vec.x = 1.0;
vec.y = 2.0;
vec.z = 3.0;
double res = vec.data[i];
感谢您的回答。
【讨论】:
这保证有效吗?还是编译器特定的行为?你用其他编译器测试过吗? 那仍然是 UB,两次。 sizeof(Vec) == 24. union 为 x 和 data[0]、y 和 data[1]、z 和 data[2] 共享相同的内存。以上是关于编译器指令重新排序的主要内容,如果未能解决你的问题,请参考以下文章