I was playing around with some code today, and was very surprised to find out that the following code crashes in Visual C++. Does it look OK? gcc prints out 1 when I run the code. So does Intel.
#include <iostream>
#include <set>
#include <assert.h>
int main()
{
std::set<int> sampleSet;
sampleSet.insert(1);
sampleSet.insert(2);
sampleSet.insert(3);
std::set<int>::iterator normalIt(sampleSet.begin());
std::set<int>::reverse_iterator reverseIt(sampleSet.rbegin());
++normalIt;
++reverseIt;
int test1(*reverseIt); // 2
assert(*normalIt == *reverseIt); //they're both = 2
std::set<int>::iterator gonnaDelete((++reverseIt).base()); // gonnaDelete points to 2
int test2(*reverseIt); // 1
sampleSet.erase(gonnaDelete);
int test3(*reverseIt); // 1??? Visual Studio 2010 crashes here.... gcc is fine, but not sure if this is legal
std::cout << test3 << std::endl;
return 0;
}
It’s not legal. According to [lib.associative.reqmts] in C++98, “the erase members shall invalidate only iterators and references to the erased elements.” Since reverse_iterator is defined something like:
when you erase
gonnaDeleteyou also invalidatereverseIt.current, so when you subsequently dereferencereverseIt, you get undefined behavior.Now, set iterators tend to be simple pointers to nodes, and optimized allocators sometimes leave the memory of a deleted node alone, so the code sometimes happens to do what you expect even after you’ve invalidated one of them, but it’s not at all guaranteed to work. I suspect VC++ has a debugging mode on by default to catch this kind of mistake. To turn on gcc’s equivalent mode, try building with
-D_GLIBCXX_DEBUG: http://gcc.gnu.org/onlinedocs/gcc-4.6.2/libstdc++/manual/manual/bk01pt03ch17s03.html