-1

昨天遇到这个问题,我将尝试给出明确而简单的示例,这些示例对我来说失败了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

虽然解决方案很明显,但防止用户像其他一些库一样使用基于指针的传输直接传递临时变量,我希望实际上让它更优雅一点,而不会阻塞视图。

4

1 回答 1

0

您可以向结构添加(非虚拟)成员函数,而不会真正影响布局。因此,当结构被破坏时,添加析构函数以打印“I'm here %p”,并在您的函数中打印“I'm there”。(包括这个地址,您可以了解正在使用的其他临时副本)。

然后您可以观察优化代码中的生命周期。看看这是否是你的问题:我怀疑糟糕的生命周期实际上意味着什么,因为它所在的位置仍然是堆栈帧中的有效地址。

此外,更改 floatnis 应该存在的位在最坏的情况下可能会给您一个非数字或特殊值,并且在这种情况下向量处理不会抛出或出错,而是将标志值作为该错误的结果元素。 没有指针,为什么要取消引用 -1 呢?

我认为失火是由更有趣的事情引起的。

在调试器中运行它,看看是什么指令导致的。

于 2015-03-07T08:49:42.870 回答