Разница в правилах конца жизни?

https://en.cppreference.com/w/cpp/language/lifetime в разделе " Примечания " содержит этот код, воспроизведенный здесь:

struct A {
  int* p;
  ~A() { std::cout << *p; } // if n outlives a, prints 123
};
void f() {
  A a;
  int n = 123; // if n does not outlive a, this is optimized out (dead store)
  a.p = &n;
}

Что он пытается сказать в этом разделе заметок?

Из того, что я понимаю, код UB (или он), так как ясно, что n не переживает a.

Что это значит под:

разница в правилах окончания срока службы между объектами, не относящимися к классу (конец срока хранения), и объектами класса (обратный порядок построения)

Но это не говорит о том, как.

Я очень смущен всем этим разделом.

Ответ 1

Это странный аспект правил жизни C++. [basic.life]/1 говорит нам, что время жизни объекта заканчивается:

  • если T является типом класса с нетривиальным деструктором ([class.dtor]), начинается вызов деструктора, или
  • память, занимаемая объектом, освобождается или используется объектом, который не вложен в o ([intro.object]).

Акцент добавлен. int не является "типом класса с нетривиальным деструктором", поэтому его время жизни заканчивается только тогда, когда освобождается хранилище, которое он занимает. Напротив, A является типом класса с нетривиальным деструктором ", поэтому его время жизни заканчивается, когда вызывается деструктор.

Хранилище для области освобождается при выходе из области в соответствии с [basic.stc.auto]/1:

Хранение для [переменных с автоматической продолжительностью хранения] длится до выхода из блока, в котором они были созданы.

Но автоматические переменные уничтожаются в соответствии с [stmt.jump]/2:

При выходе из области (однако выполненной) объекты с автоматическим сроком хранения, которые были созданы в этой области, уничтожаются в порядке, обратном их построению.

Обратите внимание, что указан порядок уничтожения, но не указан порядок автоматического освобождения хранилища. Это означает, что реализация может освободить хранилище сразу после уничтожения каждой переменной, либо освободить все сразу, либо в произвольном другом порядке.

Теперь тот факт, что он использует единственное число для хранения ("хранилище для... длится") вместо того, чтобы говорить о каждой переменной в отдельности, может указывать на то, что цель состоит в том, чтобы хранилище в целом было сразу освобождено для этой области. Но в стандарте нет явного заявления об этом. Таким образом, до тех пор, пока переменная уничтожается до освобождения ее хранилища, любой порядок уничтожения и выпуска представляется законным.

Это означает, что код вполне может работать, а n пережить a. Но не определено, работает ли оно.

Ответ 2

Этот пример заимствован из базовой языковой проблемы 2256:

Раздел: 6.8 [basic.life] Статус: составитель Автор : Ричард Смит Дата: 2016-03-30

В соответствии с 6.4 [basic.lookup] пуль 1.4, следующий пример определил поведение, потому что время жизни n простирается до его хранения не будет освобождено, что после того, как a деструктор трасс:

  void f() { 
    struct A { int *p; ~A() { *p = 0; } } a; 
    int n; 
    a.p = &n; 
  } 

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

Примечания от встречи в марте 2018 года:

CWG согласилась с предложенным направлением.

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

  1. если время жизни n заканчивается при уничтожении n, программа не определена;

  2. если время жизни n заканчивается до освобождения памяти, программа определила поведение 1.

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


1 Это потому, что основной язык проблема 2115:

Раздел: 9.6 [stmt.jump] Статус: составитель Автор : Ричард Смит Дата: 2015-04-16

Стандарт не устанавливает относительный порядок между уничтожением автоматических переменных при выходе из блока и освобождением хранилища переменных: сначала выполняются все деструкторы, а затем освобождается хранилище или чередуются?

Примечания к февральской встрече 2016 года:

CWG согласилась с тем, что хранилище должно сохраняться до тех пор, пока все разрушения не будут завершены, хотя правило "как если бы" позволяло проводить ненаблюдаемую оптимизацию этого порядка.

Намерение состоит в том, что освобождение памяти автоматических переменных происходит после того, как все разрушения завершены.