为什么GCC优化不能与valarrays一起使用?

11

这是一个使用 valarrays 的简单 C++ 程序:

#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    const auto& res ( ratios_t::value_type(256) / a );
    for(const auto& r : ratios_t{res})
        std::cout << r << " " << std::endl;
    return 0;  
}

如果我像这样编译和运行代码:

g++ -O0 main.cpp && ./a.out

输出结果如预期:

512 256 128 

然而,如果我这样编译和运行它:

g++ -O3 main.cpp && ./a.out

输出结果为:
0 0 0 

如果我使用-O1优化参数,同样的情况也会发生。

GCC版本为(在Archlinux中最新):

$ g++ --version
g++ (GCC) 6.1.1 20160707

然而,如果我使用clang尝试,则两者

clang++ -std=gnu++14 -O0 main.cpp && ./a.out

and
clang++ -std=gnu++14 -O3 main.cpp && ./a.out

产生相同正确的结果:

512 256 128 

Clang版本为:

$ clang++ --version
clang version 3.8.0 (tags/RELEASE_380/final)

我也尝试过在Debian上使用GCC 4.9.2,可执行文件产生了正确的结果。

这是GCC可能存在的一个bug,还是我做错了什么?有人能复现这个问题吗?

编辑:我成功地在Mac OS上使用Homebrew版本的GCC 6复现了这个问题。


使用 http://melpon.org/wandbox,从4.9.3到5.1似乎行为发生了变化。 - NathanOliver
不幸的是,在我的代码库中,我也成功地复制了类似的问题(但使用uint32_t),即使在GCC 4.9.3上也是如此,但当放入最小示例时它可以工作。我正在调查... - DoDo
2个回答

6
valarrayauto组合使用效果不佳。 这会创建一个临时对象,然后对其应用operator/
const auto& res ( ratios_t::value_type(256) / a );
libstdc++的valarray使用表达式模板,因此operator/返回一个轻量级对象,该对象引用原始参数并懒惰地对它们进行评估。您可以使用const auto&,这会将表达式模板绑定到引用上,但不会扩展表达式模板所引用的临时对象的生命周期。因此,在进行评估时,临时对象已经超出了作用范围,并且其内存已被重用。
如果您这样做,它将正常工作:
ratios_t res = ratios_t::value_type(256) / a;

更新:截至今天,GCC主干将为此示例提供预期结果。我已经修改了我们的valarray表达式模板,使其更不容易出错,这样就更难(但仍然不是不可能)创建悬空引用。新的实现应该会在明年的GCC 9中包含。


感谢您提供详细的答案。我会接受它。我只想指出,即使我写了 auto res (ratios_t::value_type(256)/a),在开启优化的情况下仍然会得到相同的结果。但是,您的建议有效(即不使用 auto)。在clang和msvc中,原始代码都可以正常工作。 - DoDo
是的,因为auto推断表达式模板的类型,该模板包含对已过期临时对象的引用。您需要显式创建一个valarray,以强制在临时对象消失之前进行评估。正如我所说,autovalarray不搭配使用。 - Jonathan Wakely

3
这是对于`operator/ (const T& val, const std::valarray<T>& rhs)`的粗心实现(以及可能存在的其他valarrays运算符)使用惰性求值的结果:
#include <iostream>
#include <valarray>

int main() {
    using ratios_t = std::valarray<float>;

    ratios_t a{0.5, 1, 2};
    float x = 256;
    const auto& res ( x / a );
    // x = 512;  //  <-- uncommenting this line affects the output
    for(const auto& r : ratios_t{res})
        std::cout << r << " ";
    return 0;
}

当注释掉"x = 512"这一行时,输出结果为:

512 256 128 

取消注释该行,输出结果会发生变化

1024 512 256 

由于在您的示例中,除法运算符的左侧参数是一个临时变量,因此结果未定义。

更新

正如Jonathan Wakely正确指出的那样,在这个示例中,基于惰性评估的实现由于使用了auto而成为一个问题。


谢谢你的回答。Jonathan先回答了,所以我接受了他的答案,但是你的答案也很有用。太遗憾了,SO不允许你接受多个答案 :-). - DoDo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接