Неожиданный результат, когда 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() может или не может перераспределяться, и вы не можете узнать, будет ли это так, поведение не определено.