Puzzled by Debug Assertion

I'm getting

Debug Assertion Failed
[File and line equivalent to snippet line 36]
Expression: Vector erase iterator outside range


This snippet is not meant to execute, only to show that it compiles cleanly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <vector>
#include <cassert>

class TILE;
class PILE;

typedef std::vector<TILE>   tilevec_t;
typedef char                LETTER;

class TILE
{
    LETTER      m_ltr;
};

class RACK
{
public:
    tilevec_t       mv_tiles;

    TILE Remove_Tile(LETTER ltr);
    bool isValid();
    tilevec_t::iterator Find(LETTER ltr);
};

//  Remove tile from both mv_tiles and PILE
TILE RACK::Remove_Tile(LETTER ltr)
{
    TILE                    t;
    tilevec_t::iterator     iter;

    isValid();
    iter = Find(ltr);
    assert(iter != mv_tiles.end());    
    t = *iter;    
    assert(iter >= mv_tiles.begin() && iter < mv_tiles.end());
    mv_tiles.erase(iter);
    //  m_pile.Decr(ltr);
    isValid();
    return t;
}


Stepping through:
Line 32: Find returns a valid iterator pointing to mv_tiles[0].
Line 33: assertion passes meaning we found a matching entry
Line 34: t gets valid values.
Line 35: assertion passes indicating iter is between begin and end.
Line 36: erase traps with the above message.

I'm not understanding how iter can possibly be out of range.

Any ideas?
Do the assert statements actually pass ... or have they accidentally been switched off; e.g. with
#define NDEBUG
or a compiler option.

Does the second assertion actually cut in if you deliberately set
iter = mv_tiles.end();
just before it?

Last edited on
1) Yes, the asserts are active.
2) Yes, the second assert cuts in if I set iter to mv_tiles.end()

Here's the code from vector::erase:
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
_CONSTEXPR20_CONTAINER iterator erase(const_iterator _Where) noexcept(
        is_nothrow_move_assignable_v<value_type>) /* strengthened */ {
        const pointer _Whereptr = _Where._Ptr;
        auto& _My_data          = _Mypair._Myval2;
        pointer& _Mylast        = _My_data._Mylast;

#if _ITERATOR_DEBUG_LEVEL == 2
        _STL_VERIFY(
            _Where._Getcont() == _STD addressof(_My_data) && _Whereptr >= _My_data._Myfirst && _Mylast > _Whereptr,
            "vector erase iterator outside range");
#endif // _ITERATOR_DEBUG_LEVEL == 2\ 
Last edited on
I tried to match your code as best I could without writing a lot of code ... but I couldn't reproduce the outcome.

From the command line:

cl /std:c++20 /EHsc /MDd /D_ITERATOR_DEBUG_LEVEL=2 temp.cpp
gave sensible output if iter=mv_tiles.begin() and the pop-up expected for an invalid iterator if iter=mv_tiles.end().

g++ temp.cpp
output two of the three elements of the vector (but it should be "undefined behaviour" if iter=mv_tiles.end()).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;

class TILE;
class PILE;

typedef std::vector<TILE> tilevec_t;
typedef char              LETTER;

class TILE
{
public:
    LETTER m_ltr;
    TILE( char c = 'a' ) { m_ltr = c; }
};

class RACK
{
public:
    tilevec_t mv_tiles;
    TILE Remove_Tile(LETTER ltr);
};


TILE RACK::Remove_Tile(LETTER ltr)     // The argument is irrelevant for now
{
    tilevec_t::iterator iter;

    iter = mv_tiles.begin();           // Shouldn't cause a crash
//  iter = mv_tiles.end();             // Should cause a crash
//  assert(iter >= mv_tiles.begin() && iter < mv_tiles.end());
    mv_tiles.erase(iter);
    return TILE( 'x' );
}


int main()
{
   RACK V;
   V.mv_tiles.push_back( 'a');
   V.mv_tiles.push_back( 'b');
   V.mv_tiles.push_back( 'c');

   V.Remove_Tile( 'a' );
   for ( auto e : V.mv_tiles ) cout << e.m_ltr << ' ';
}
Last edited on
With iter = mv_tiles.end();

$ g++ -std=c++20 -g -Wall -pedantic-errors -D_GLIBCXX_DEBUG main.cpp && ./a.out

...
Error: attempt to erase from container with a past-the-end iterator.
...
 Aborted                 (core dumped)

http://coliru.stacked-crooked.com/a/e6c7ac297b62b0bc
Last edited on
Yes, I could crash it with iter=mv_tiles.end().

I believe that @AbstractionAnon's problem was that it would also crash with iter=mv_tiles.begin(), a feature that I am unable to reproduce, as my code does the expected at that point.
The iter=mv_tiles.end() was put in intentionally to prove the asserts were active.

lastchance is correct, iter=mv_tiles.begin() causes the out of range error even after passing the assert that iter is between begin() and end().


Last edited on
iter=mv_tiles.begin() causes the out of range error even after passing the assert that iter is between begin() and end()


Hi @AbstractionAnon,
Is that for your original code, or for the one that I have just posted?

When I run with
iter=mv_tiles.begin()
then the erase() operation is OK, doesn't cause an out-of-range error, and, as expected, removes the first element of the array. (I'm not trying to remove a specific letter in my code.)

The version of the compiler that I am using is
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30133 for x64
Last edited on
Yes, RACK::Remove_Tile() is my original code.
I've added the asserts to try and catch something obvious.

I decided to look at RACK::Find() a little more closely.
RACK::Find() is a simple linear search of the vector.
I know from debugging that the vector has 7 elements.
I added line 3. mv_tiiles.erase(iter) still fails with an out of range error.

1
2
3
4
5
6
7
8
tilevec_t::iterator RACK::Find(LETTER ltr) 
{
    return mv_tiles.begin();    /  Added for debugging/testing
    for (auto iter = mv_tiles.begin(); iter != mv_tiles.end(); iter++)
        if (iter->get_letter() == ltr)
            return iter;       //  Found it 
    return mv_tiles.end();      //  Not found
}


Using lastchance's main(), I have not been able to get the test program to fail.

edit: When I look at vector::erase, the key condition in line 1414 that must be failing is:
 
_Where._Getcont() == _STD addressof(_My_data)

I've checked the remaining two conditions and their values seem reasonable.

I'm slowly coming to the conclusion that I may have trashed something in the vector anchor.
I have no clue how I may have done that.
I wish vector had a function that checks the integrity of the anchor.
Last edited on
Found it.

In several places i intentionally pass RACK by value, which of course requires a copy constructor. My copy constructor was defective. As soon as I fixed that, the problem went away.

@lastchance - Thanks for your help.

Topic archived. No new replies allowed.