Неожиданный результат, когда C++ сохраняет элемент в std :: vector из возвращаемого значения функции
Когда функция включает перераспределение, я обнаружил, что некоторые компиляторы могут сохранить адрес перед вызовом функции. Он возвращает возвращаемое значение, хранящееся в неверном адресе.
В приведенном выше описании приведен пример объяснения поведения.
#include <stdio.h>
#include <vector>
using namespace std;
vector<int> A;
int func() {
A.push_back(3);
A.push_back(4);
return 5;
}
int main() {
A.reserve(2);
A.push_back(0);
A.push_back(1);
A[1] = func();
printf("%d\n", A[1]);
return 0;
}
Существует некоторый общий компилятор C++ и результат теста следующим образом.
- GCC (сборник компилятора GNU): ошибка выполнения или выход
1
- Clang: выход
5
- V C++: выход
5
Это неопределенное поведение?
Ответы
Ответ 1
Поведение не определено во всех версиях C++ до C++ 17. Простая причина заключается в том, что обе стороны оператора присваивания могут быть оценены в любом порядке:
- Предполагая, что
A[1]
оценивается первым, вы получаете int&
ссылаетесь на второй элемент A
в этой точке. - Затем вычисляется
func()
, которая может перераспределять хранилище для вектора, оставляя ранее полученную ссылку int&
a dangling. - Наконец, назначение выполняется, записывая в нераспределенное хранилище. Так как стандартная кэш-память распределителей, операционная система часто не поймает эту ошибку.
Только в C++ 17 было принято специальное правило 20 для присвоения:
В каждом простом присваивающем выражении E1 = E2 и каждом выражении присваивания E1 @= E2 каждое вычисление значения и побочный эффект E2 секвенируются перед вычислением каждого значения и побочным эффектом E1
С помощью C++ 17, A[1]
необходимо оценить после вызова func()
, который затем обеспечивает определенное, надежное поведение.
Ответ 2
Если вы проверите документацию, в разделе "Итераторская аннулирование" вы увидите, что push_back()
может аннулировать каждый итератор, если он изменяет емкость, поскольку он должен будет перераспределять память. Помните, что для std::vector
указатель также является допустимым итератором. Поскольку push_back()
может или не может перераспределяться, и вы не можете узнать, будет ли это так, поведение не определено.