反向迭代器在优化时返回垃圾

我有一个AsIterator模板类,它采用类似数字的types,在本例中只是一个int ,并将其转换为一个迭代器( ++--递增和递减数字, operator*只是返回一个引用)。

这工作正常, 除非它被包装到一个std::reverse_iterator和编译与任何优化-O是足够的)。 当我优化二进制文件时,编译器会reverse_iteratorreverse_iterator的解引用调用,并用一些奇怪的值replace它。 必须指出,它仍然会进行正确的迭代次数 。 这只是反向迭代器获得的值是垃圾。

考虑下面的代码:

 #include <iterator> #include <cstdio> template<typename T> class AsIterator : public std::iterator<std::bidirectional_iterator_tag, T> { T v; public: AsIterator(const T & init) : v(init) {} T &operator*() { return v; } AsIterator &operator++() { ++v; return *this; } AsIterator operator++(int) { AsIterator copy(*this); ++(*this); return copy; } AsIterator &operator--() { --v; return *this; } AsIterator operator--(int) { AsIterator copy(*this); --(*this); return copy; } bool operator!=(const AsIterator &other) const {return v != other.v;} bool operator==(const AsIterator &other) const {return v == other.v;} }; typedef std::reverse_iterator<AsIterator<int>> ReverseIt; int main() { int a = 0, b = 0; printf("Insert two integers: "); scanf("%d %d", &a, &b); if (b < a) std::swap(a, b); AsIterator<int> real_begin(a); AsIterator<int> real_end(b); for (ReverseIt rev_it(real_end); rev_it != ReverseIt(real_begin); ++rev_it) { printf("%d\n", *rev_it); } return 0; } 

这应该假设从最高插入的数字向下循环,并打印出来,比如在这个运行中(用-O0编译):

 Insert two integers: 1 4 3 2 1 

我用-O得到的是:

 Insert two integers: 1 4 1 0 0 

你可以在网上试试 数字可能会有所不同,但在优化二进制文件时总是“错误”。


我试过了:

  • 对input整数进行硬编码足以产生相同的结果;
  • 这个问题在gcc 5.4.0clang 3.8.0中仍然存在,在使用libc ++时也是如此。
  • 使所有的对象为const (即返回const int & ,并声明所有的variables)不会修复它;
  • 例如一些std::vector<int>工作正常,使用reverse_iterator也是如此。
  • 如果我只是使用AsIterator<int>作为正常的向前或向后循环,它工作正常。
  • 在我的testing中,打印出来的常量0实际上是由编译器硬编码的,调用printf在使用-S -O编译时看起来都是这样的:
  movl $.L.str.2, %edi # .L.str.2 is "%d\n" xorl %eax, %eax callq printf 

鉴于clanggcc在这里的行为是一致的,我很确定他们做对了,我误解了,但我真的看不到它。

看着std::reverse_iteratorlibstdc ++实现,揭示了一些有趣的东西:

  /** * @return A reference to the value at @c --current * * This requires that @c --current is dereferenceable. * * @warning This implementation requires that for an iterator of the * underlying iterator type, @cx, a reference obtained by * @c *x remains valid after @cx has been modified or * destroyed. This is a bug: http://gcc.gnu.org/PR51823 */ _GLIBCXX17_CONSTEXPR reference operator*() const { _Iterator __tmp = current; return *--__tmp; } 

@warning部分告诉我们,底层迭代器types的一个要求就是*x必须保持有效,即使在底层迭代器被修改/销毁之后。

看着提到的错误链接显示更有趣的信息:

在C ++ 03和C ++ 11之间的某个时刻,reverse_iterator :: operator *的定义被改变,以澄清这一点,使得libstdc ++的实现错误。 标准现在说:

[注意:此操作必须使用辅助成员variables而不是临时variables,以避免返回超出其关联迭代器生命期的引用。 (见24.2。) – 结束注释]

Jonathan Wakely评论(2012)

所以它看起来像一个bug …但在主题的最后:

reverse_iterator的定义已经恢复到C ++ 03版本,它不使用额外的成员,所以“存储迭代器”不能与reverse_iterator一起使用。

Jonathan Wakely评论(2014)

所以看起来,使用std::reverse_iterator和“存储迭代器”确实会导致UB。


查看DR 2204: reverse_iterator不应该需要基本迭代器的第二个副本”进一步阐明了这个问题:

24.5.1.3.4 [reverse.iter.op.star] / 2:

[注意:此操作必须使用辅助成员variables而不是临时variables,以避免返回超出其关联迭代器生命期的引用。 (见24.2。) – 结束注释]

[我的说明:我认为上述说明可以解决您的UB问题]

是不正确的,因为这样的迭代器实现被24.2.5 [forward.iterators] / 6排除,它说:

如果a和b都是可解引用的,则a == b当且仅当* a和* b绑定到同一个对象。