Strange output from dereferencing pointers into a vector

爷,独闯天下 提交于 2021-02-08 11:59:27

问题


I was writing a program in C++ where I need to have a 2d grid of pointers which point to objects which are stored in a vector. I tested out some part of the program and saw strange results in the output.

I changed the objects to integers and removed everything non-essential to cut it down to the code snippet below, but I still get a weird output.

vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers

vector<int> relevant;//vector carrying actual values onto which pointers will point

for(int i = 0; i<10; i++){
    int new_integer = i;
    relevant.push_back(new_integer);//insert integer into vector
    lattice[0][i] = &relevant[i];//let pointer point onto this value
}

//OUTPUT
for(int j = 0; j<10; j++){
    cout<<*lattice[0][j]<<" ";
    cout<<relevant[j]<<endl;
}

I get strange outputs like this:

19349144 0
19374040 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9

Also, my output changes from run to run and depending on how big/small I make my grid.

I would expect all the values on the left and right to be equal, I guess there is something fundamental about pointers I haven't understood, so sorry if this is a very basic question.

Can someone please explain why I get strange outputs for some values of the grid?


回答1:


When a std::vector is inserted into, if its new size() will exceed its current capacity(), the vector has to reallocate its internal array to make room, which will invalidate any existing iterators and pointers to the old memory.

In your example, you can avoid that by reserve()'ing the capacity() ahead of time, eg:

vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers

vector<int> relevant;//vector carrying actual values onto which pointers will point

//ADD THIS!
relevant.reserve(10);//pre-allocate the capacity

for(int i = 0; i<10; i++){
    int new_integer = i;
    relevant.push_back(new_integer);//insert integer into vector
    lattice[0][i] = &relevant[i];//let pointer point onto this value
}

//OUTPUT
for(int j = 0; j<10; j++){
    cout<<*lattice[0][j]<<" ";
    cout<<relevant[j]<<endl;
}

Alternatively, you can pre-allocate the size() and then use operator[] instead of push_back(), eg:

vector<vector<int*>> lattice(10, vector<int*>(10));//grid of pointers

vector<int> relevant(10);//vector carrying actual values onto which pointers will point

for(int i = 0; i<10; i++){
    int new_integer = i;
    relevant[i] = new_integer;//insert integer into vector
    lattice[0][i] = &relevant[i];//let pointer point onto this value
}

//OUTPUT
for(int j = 0; j<10; j++){
    cout<<*lattice[0][j]<<" ";
    cout<<relevant[j]<<endl;
}



回答2:


I need to have a 2d grid of pointers which point to objects which are stored in a vector

That's impossible. Or rather, that's a guarantee for dereferencing invalid addresses. Why?

At any given time, an std::vector has enough space allocated for some limited number of elements. If you keep adding elements to it, it will eventually max out its storage. At some insertion, it will decide allocate a new stretch of memory to use for storing its data; move (or copy) existing data to the new storage area; free the old storage area; and then be able to add more elements.

When this happens, all existing pointers into objects in the vector become invalid. The memory they point to may continue to hold the previous values, but may also be used to store other data - there are no guarantees on that! In fact, dereferencing invalid pointers officially results in undefined behavior.

... and I do see that what I've described is exactly what you're doing with your code. Your older pointers become invalid.

Instead, consider keeping indices into the vector rather than pointers. Indices don't get invalidated by adding elements to the vector, and you can keep using them.


PS - I see you're using a vector-of-vectors. That's technically valid, but it is often unadvisable. Consider using a matrix class (e.g. from the Eigen library) or allocating a certain amount of memory using std::make_unique(), then using it to initialize a gsl::multi_span.




回答3:


relevant.push_back invalidates all pointers/references/iterators to its elements (if the new size exceeds its current capacity).

Therefore you are dereferencing potentially invalid pointers when you do

*lattice[0][j]

later.

You can use a container that doesn't invalidate on insertion at the end, such as std::list or std::deque, instead of std::vector, for relevant.

(Or you can reserve a sufficient capacity with a call to .reserve first, so that the size on the .push_back operations will never exceed the capacity and will therefore never invalidate pointers, but that carries the risk of easily accidentally ignoring this requirement on later code changes, again causing UB.)



来源:https://stackoverflow.com/questions/60792327/strange-output-from-dereferencing-pointers-into-a-vector

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!