反向迭代器在优化时返回垃圾
我有一个AsIterator
模板类,它采用类似数字的types,在本例中只是一个int
,并将其转换为一个迭代器( ++
和--
递增和递减数字, operator*
只是返回一个引用)。
这工作正常, 除非它被包装到一个std::reverse_iterator
和编译与任何优化 ( -O
是足够的)。 当我优化二进制文件时,编译器会reverse_iterator
对reverse_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.0和clang 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
鉴于clang和gcc在这里的行为是一致的,我很确定他们做对了,我误解了,但我真的看不到它。
看着std::reverse_iterator
的libstdc ++实现,揭示了一些有趣的东西:
/** * @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绑定到同一个对象。