为什么GCC -O3在filter迭代器上通过std :: deque产生无限的std :: distance?

经过很多的痛苦和苦难之后,我跟踪了一些非常奇怪的行为,当std::distance在给定范围的boost::filter_iterator时, std::distance永远不会返回std::deque 。 看来这个问题对于使用-O3优化的GCC(6.1+)来说是独一无二的。 这是一个certificate违规行为的例子:

 #include <string> #include <deque> #include <iterator> #include <iostream> #include <boost/iterator/filter_iterator.hpp> struct Foo { std::string bar, s = ""; char a = '\0'; }; int main() { const std::deque<Foo> foos(14, {""}); const std::string test {}; const auto p = [test] (const auto& foo) { return foo.bar == test; }; using boost::make_filter_iterator; const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos)); const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos)); std::cout << std::distance(begin, end) << std::endl; } 

一些观察:

  • GCC优化了-O2或更less的回报如预期。
  • 铿锵声(3.8)返回任何优化级别的正确答案。
  • std::deque更改为std::vectorstd::list导致预期的行为。
  • 14是至关重要的; 更less的东西就会消失。
  • sizeof(Foo)是重要的; 删除sa使问题消失。
  • 通过引用捕获test ,或仅仅比较常量expression式(例如foo.bar == " " )会导致正常行为。
  • 没有编译器警告(使用-Wall -Wextra -pedantic )。
  • Valgrind报告没有错误。
  • 使用fsanitize=undefined ,问题就消失了。

这是怎么回事?

我认为,下面的这些发现对于改进错误报告以及在代码中解决问题都是有用的。

通过debugging优化的输出,并使用优化标志和less量的代码更改,我已经得出了关于导致错误的特定优化标志的结论。

这套选项是:

 -O -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-guess-branch-probability -fno-if-conversion2 -fno-if-conversion -fno-inline-functions-called-once -fno-ipa-pure-const -fno-ipa-profile -fno-ipa-reference -fno-merge-constants -fno-move-loop-invariants -fno-reorder-blocks -fno-shrink-wrap -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-phiprop -fno-tree-sink -fno-tree-slsr -fno-tree-dse -fno-tree-forwprop -fno-tree-fre -fno-unit-at-a-time -fno-tree-ter -fno-tree-sra -fno-tree-copy-prop -fstrict-aliasing -ftree-slp-vectorize -std=c++14 

对不起,这一套,但我真正想要的是这样的: -O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize (我也试过-Og)再加上O1的魔力

请注意,只是-O3 -f-no-tree-slp-vectorize已经可以解决这个问题了,但是通过使用完整的选项,我发送的debugging几乎变得简单…

此外,它看起来像运算符==(string, string)正在编译器中产生混淆。

如果你检查粘贴的代码在哪里,所有的注释#if 0代码,当被激活时代替原代码,你可能会发现我没有的地方的问题。

请注意, ==()运算符甚至没有被调用,因为foo.a != '\0'在testing中始终为真。 因此,它看起来是存在使编译器生成错误的代码。

还要注意,循环内的任何注释代码也会将行为更改为预期的行为,这就是为什么我怀疑启动器的向量化标志。

 #include <string> #include <deque> #include <iterator> #include <iostream> #include <boost/iterator/filter_iterator.hpp> #include <string.h> struct Foo { std::string bar, s = ""; char a = 'n'; }; std::ostream& operator<<(std::ostream& os, const Foo& f) { os << f.bar << '/' << fa; return os; } int main() { std::deque<Foo> foos(14, {"abc"}); const std::string test {"abc"}; Foo other; other.bar = "last"; other.a = 'l'; foos.push_back(other); other.bar = "first"; other.a = 'f'; foos.push_front(other); // #if 0 const auto p = [test] (const auto& foo) { return foo.a != '\0'; }; #elif 0 const auto p = [test] (const auto& foo) { bool rc = (foo.a != '\0'); if (!rc) rc = (foo.bar == std::string(test)); return rc; }; #elif 1 const auto p = [test] (const auto& foo) { bool rc = (foo.a != '\0'); if (!rc) rc = (foo.bar == test); return rc; }; #endif using boost::make_filter_iterator; const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos)); const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos)); std::cout << std::distance(end, end) << std::endl; std::cout << std::distance(begin, begin) << std::endl; std::cout << std::distance(std::cbegin(foos), std::cend(foos)) << std::endl; auto __first = begin; auto __last = end; int __n = 0; //std::cout << __last << std::endl; //std::deque<char> trace; //Foo trace[21]; const int max = foos.size(); char trace[max+5]; memset(trace, 'c', sizeof(trace)); std::cout << max << std::endl; std::cout << *__last << std::endl; while (__first != __last) { trace[__n] = (*__first).a; //trace[__n] = (*__first); //trace.push_back((*__first).a); //std::cout << *__first << std::endl; ++__n; ++__first; if (__n > max + 5) break; //std::cout << __n << std::endl; //std::cout << (__first != __last) << std::endl; } for (auto f: trace) std::cout << f << std::endl; std::cout << "Tadaaaaa: " << __n << std::endl; //std::cout << std::distance(begin, end) << std::endl; } 

这种行为是由于不良vector化优化引起的GCC错误引起的。 现在已经发布了一个修复程序,它应该出现在GCC 6.3中。

对于那些坚持GCC 5.4 – 6.2,编译器选项-fno-tree-slp-vectorize将'修复'的问题。