Странный вывод указателей разыменования в вектор - PullRequest
0 голосов
/ 21 марта 2020

Я писал программу на C ++, где мне нужно иметь двумерную сетку указателей, которые указывают на объекты, которые хранятся в векторе. Я протестировал некоторую часть программы и увидел странные результаты в выводе.

Я изменил объекты на целые числа и удалил все несущественное, чтобы сократить его до приведенного ниже фрагмента кода, но я все еще получаю странный вывод.

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;
}

Я получаю странные выводы, такие как this:

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

Кроме того, мой вывод меняется от прогона к прогону и зависит от того, насколько большой / маленький я делаю свою сетку.

Я бы ожидал, что все значения слева и справа будут быть равным, я думаю, что есть что-то фундаментальное в указателях, которые я не понял, поэтому извините, если это очень простой c вопрос.

Может кто-нибудь объяснить, почему я получаю странные выходные данные для некоторых значений сетка

Ответы [ 3 ]

3 голосов
/ 21 марта 2020

Мне нужна двумерная сетка указателей, которые указывают на объекты, хранящиеся в векторе

Это невозможно. Вернее, это гарантия разыменования недействительных адресов. Почему?

В любой момент времени std::vector имеет достаточно места для некоторого ограниченного числа элементов. Если вы продолжите добавлять к нему элементы, это в конечном итоге приведет к максимальному увеличению его хранилища. В какой-то момент он решит выделить новый участок памяти для хранения своих данных; переместить (или скопировать) существующие данные в новую область хранения; освободить старые складские помещения; и затем сможете добавить больше элементов.

Когда это происходит, все существующие указатели на объекты в векторе становятся недействительными . Память, на которую они указывают, может продолжать хранить предыдущие значения, но может также использоваться для хранения других данных - на это нет никаких гарантий! Фактически, разыменование недействительных указателей официально приводит к неопределенному поведению .

... и я вижу, что я описал именно то, что вы делаете со своим кодом. Ваши старые указатели становятся недействительными.

Вместо этого рассмотрите возможность сохранения индексов в векторе, а не в указателях. Индексы не становятся недействительными, добавляя элементы к вектору, и вы можете продолжать их использовать.


PS - Я вижу, вы используете вектор векторов. Это технически правильно, но часто это не рекомендуется. Попробуйте использовать класс матрицы (например, из библиотеки Eigen) или выделить определенный объем памяти, используя std::make_unique(), а затем использовать его для инициализации gsl::multi_span.

1 голос
/ 21 марта 2020

relevant.push_back делает недействительными все указатели / ссылки / итераторы для своих элементов (если новый размер превышает его текущую емкость).

Поэтому вы разыменовываете потенциально недействительные указатели, когда выполняете

*lattice[0][j]

позже.

Вы можете использовать контейнер, который не делает недействительным при вставке в конце, например std::list или std::deque вместо std::vector, для relevant.

(Или вы можете зарезервировать достаточную емкость, сначала позвонив по номеру .reserve, так что размер операций .push_back никогда не будет превышать емкость и, следовательно, никогда не сделает недействительными указатели, но это может привести к риску случайного игнорирования этого требования при последующих изменениях кода, что снова приведет к UB.)

0 голосов
/ 21 марта 2020

Когда std::vector вставлен в, если его новый size() превысит его текущий capacity(), вектор должен перераспределить свой внутренний массив, чтобы освободить место, что сделает недействительными любые существующие итераторы и указатели на старую память .

В вашем примере вы можете избежать этого, reserve() с опережением capacity(), например:

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;
}

В качестве альтернативы, вы можете предварительно выделить size() и затем используйте operator[] вместо push_back(), например:

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...