Puzzled by Debug Assertion

Nov 9, 2021 at 6:19pm
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?
Nov 9, 2021 at 7:13pm
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 Nov 9, 2021 at 7:15pm
Nov 9, 2021 at 7:49pm
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 Nov 9, 2021 at 7:57pm
Nov 10, 2021 at 2:11pm
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 Nov 10, 2021 at 2:35pm
Nov 10, 2021 at 2:40pm
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 Nov 10, 2021 at 2:41pm
Nov 10, 2021 at 3:03pm
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.
Nov 10, 2021 at 4:50pm
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 Nov 10, 2021 at 7:28pm
Nov 10, 2021 at 4:55pm
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 Nov 10, 2021 at 4:57pm
Nov 10, 2021 at 7:52pm
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 Nov 10, 2021 at 9:52pm
Nov 10, 2021 at 10:45pm
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.