这是一个使用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
和
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中可能存在的错误还是我做错了什么?任何人都可以重现这个吗?
编辑:我设法在Mac OS上的Homebrew版本的GCC 6上重现了这个问题。答案 0 :(得分:6)
valarray
和auto
混合不好。
这将创建一个临时对象,然后将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中纳入。
答案 1 :(得分: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;
}
使用&#34; x = 512
&#34;行注释掉,输出是
512 256 128
取消注释该行,输出更改为
1024 512 256
因为在你的例子中,除法运算符的左侧参数是临时的,结果是未定义的。
<强>更新强>
正确地Jonathan Wakely pointed out,由于auto
的使用,基于延迟评估的实现在此示例中成为问题。