昨天遇到这个问题,我将尝试给出明确而简单的示例,这些示例对我来说失败了MSVC12 (VS2013, 120)和MSVC14 (VS2015, 140)。一切都是隐含的 /arch:SSE+ x64。
出于说明目的,我将使用定义的宏 _MM_TRANSPOSE4_PS 将问题简化为一个简单的矩阵转置示例。这个是根据 shuffle 实现的,而不是移动 L/H 8 字节块。
float4x4 Transpose(const float4x4& m) {
matrix4x4 n = LoadMatrix(m);
_MM_TRANSPOSE4_PS(n.row[0], n.row[1], n.row[2], n.row[3]);
return StoreMatrix(n);
}
这matrix4x4
只是一个包含四个__m128
成员的 POD 结构,所有内容都整齐地排列在 16 字节边界上,尽管它有些隐含:
__declspec(align(16)) struct matrix4x4 {
__m128 row[4];
};
所有这些都在 /O1、/O2 和 /Ox 上失败:
// Doesn't work.
float4x4 resultsPlx = Transpose( GiveMeATemporary() );
// Changing Transpose to take float4x4, or copy a temporary
float4x4 Transpose(float4x4 m) { ... }
// Trying again, doesn't work.
float4x4 resultsPlx = Transpose( GiveMeATemporary() );
奇怪的是,这有效:
// A constant reference to an rvalue, a temporary
const float4x4& temporary = GiveMeATemporary();
float4x4 resultsPlx = Transpose(temporary);
基于指针的传输也是如此,这是合乎逻辑的,因为底层机制是相同的。C++11 规范的相关部分是§12.2/5:
第二个上下文是引用绑定到临时的。引用绑定到的临时对象或作为临时对象绑定的子对象的完整对象的临时对象将在引用的生命周期内持续存在,除非下面指定。临时绑定到构造函数的 ctor-initializer (§12.6.2 [class.base.init]) 中的引用成员将持续存在,直到构造函数退出。临时绑定到函数调用(第 5.2.2 节 [expr.call])中的引用参数会一直持续到包含调用的完整表达式完成为止。
这意味着它应该一直存在,直到调用环境超出范围,这在函数返回之后很远。那么,什么给了?在所有其他情况下,变量会“优化掉”,但以下情况除外:
Access violation reading location 0xFFFFFFFFFFFFFFFF
虽然解决方案很明显,但防止用户像其他一些库一样使用基于指针的传输直接传递临时变量,我希望实际上让它更优雅一点,而不会阻塞视图。