优化后反向迭代器 returns 垃圾
Reverse iterator returns garbage when optimized
我有一个 AsIterator
模板 class,它采用类似数字的类型,在此示例中只是一个 int
,并将其转换为迭代器(++
--
递增和递减数字,operator*
只是 returns 对它的引用)。
这工作正常除非它被包装到std::reverse_iterator
并使用任何优化 编译(-O
就足够了)。当我优化二进制文件时,编译器会删除对 reverse_iterator
的取消引用调用,并将其替换为一些奇怪的值。必须注意的是,它仍然 进行了正确的迭代次数 。只是反向迭代器得到的值是垃圾。
考虑以下代码:
#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
你可以try it online here;数字可能会有所不同,但在优化二进制文件时它们总是 "wrong"。
我尝试过的:
- 对输入整数进行硬编码足以产生相同的结果;
- 问题仍然存在 gcc 5.4.0 和 clang 3.8.0,使用 libc++[= 时也是如此69=];
- 制作所有对象
const
(即返回 const int &
,并声明所有变量)并不能解决问题;
- 以相同的方式在某些
std::vector<int>
上使用 reverse_iterator
效果很好;
- 如果我只是将
AsIterator<int>
用于正常的前向或后向循环,它就可以正常工作。
- 在我的测试中,打印的常量
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, @c x, a reference obtained by
* @c *x remains valid after @c x 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
部分告诉我们,底层迭代器类型的要求是 *x
必须保持有效,即使在底层迭代器是 modified/destroyed.
之后
查看 mentioned bug link 揭示了更多有趣的信息:
at some point between C++03 and C++11 the definition of reverse_iterator::operator* was changed to clarify this, making libstdc++'s implementation wrong. The standard now says:
[ Note: This operation must use an auxiliary member variable rather than a temporary variable to avoid returning a reference that persists beyond the lifetime of its associated iterator. (See 24.2.) —end note ]
Jonathan Wakely 的评论 (2012)
所以看起来像个bug...不过在话题的最后:
The definition of reverse_iterator has been reverted to the C++03 version, which does not use an extra member, so "stashing iterators" can not be used with reverse_iterator.
Jonathan Wakely 的评论 (2014)
所以看起来 std::reverse_iterator
和 "stashing iterators" 一起使用确实会导致 UB。
查看 DR 2204: "reverse_iterator
should not require a second copy of the base iterator" 进一步阐明了问题:
This note in 24.5.1.3.4 [reverse.iter.op.star]/2:
[ Note: This operation must use an auxiliary member variable rather than a temporary variable to avoid returning a reference that persists beyond the lifetime of its associated iterator. (See 24.2.) —end note ]
[my note: I think that the above note would fix your UB issue]
是不正确的,因为这样的迭代器实现被 24.2.5 [forward.iterators]/6 排除了,它说:
If a and b are both dereferenceable, then a == b if and only if *a and *b are bound to the same object.
我有一个 AsIterator
模板 class,它采用类似数字的类型,在此示例中只是一个 int
,并将其转换为迭代器(++
--
递增和递减数字,operator*
只是 returns 对它的引用)。
这工作正常除非它被包装到std::reverse_iterator
并使用任何优化 编译(-O
就足够了)。当我优化二进制文件时,编译器会删除对 reverse_iterator
的取消引用调用,并将其替换为一些奇怪的值。必须注意的是,它仍然 进行了正确的迭代次数 。只是反向迭代器得到的值是垃圾。
考虑以下代码:
#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
你可以try it online here;数字可能会有所不同,但在优化二进制文件时它们总是 "wrong"。
我尝试过的:
- 对输入整数进行硬编码足以产生相同的结果;
- 问题仍然存在 gcc 5.4.0 和 clang 3.8.0,使用 libc++[= 时也是如此69=];
- 制作所有对象
const
(即返回const int &
,并声明所有变量)并不能解决问题; - 以相同的方式在某些
std::vector<int>
上使用reverse_iterator
效果很好; - 如果我只是将
AsIterator<int>
用于正常的前向或后向循环,它就可以正常工作。 - 在我的测试中,打印的常量
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, @c x, a reference obtained by
* @c *x remains valid after @c x 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
部分告诉我们,底层迭代器类型的要求是 *x
必须保持有效,即使在底层迭代器是 modified/destroyed.
查看 mentioned bug link 揭示了更多有趣的信息:
at some point between C++03 and C++11 the definition of reverse_iterator::operator* was changed to clarify this, making libstdc++'s implementation wrong. The standard now says:
[ Note: This operation must use an auxiliary member variable rather than a temporary variable to avoid returning a reference that persists beyond the lifetime of its associated iterator. (See 24.2.) —end note ]
Jonathan Wakely 的评论 (2012)
所以看起来像个bug...不过在话题的最后:
The definition of reverse_iterator has been reverted to the C++03 version, which does not use an extra member, so "stashing iterators" can not be used with reverse_iterator.
Jonathan Wakely 的评论 (2014)
所以看起来 std::reverse_iterator
和 "stashing iterators" 一起使用确实会导致 UB。
查看 DR 2204: "reverse_iterator
should not require a second copy of the base iterator" 进一步阐明了问题:
This note in 24.5.1.3.4 [reverse.iter.op.star]/2:
[ Note: This operation must use an auxiliary member variable rather than a temporary variable to avoid returning a reference that persists beyond the lifetime of its associated iterator. (See 24.2.) —end note ]
[my note: I think that the above note would fix your UB issue]
是不正确的,因为这样的迭代器实现被 24.2.5 [forward.iterators]/6 排除了,它说:
If a and b are both dereferenceable, then a == b if and only if *a and *b are bound to the same object.