Как избежать создания какого-либо объекта в хранилище, которое занимал автоматический объект const?

Стандарт C ++ (черновик) содержит то, что я называю «предложением о пригодности для использования», то есть [базовый .life] / 10:

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

Первая часть хороша: «статика, поток», «продолжительность хранения». Было бы неразумно разрешать повторное использование такого хранилища.

А как насчет последней части:

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

Означает ли это, что пользователю следует избегать создания каких-либо объектов в любом месте памяти, которое могло быть использовано стеком (для хранения автоматических объектов)?

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

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


person curiousguy    schedule 13.12.2019    source источник
comment
Константа имеет значение.   -  person molbdnilo    schedule 13.12.2019
comment
@molbdnilo Значительно как?   -  person curiousguy    schedule 13.12.2019
comment
Потому что компилятор хочет иметь возможность предположить, что объект const не изменяется.   -  person molbdnilo    schedule 13.12.2019
comment
@molbdnilo (а) Я не понимаю, насколько это актуально. Почему тогда пользователю не разрешено повторно использовать хранилище константного объекта? (б) Вы утверждаете, что это незаконно: void f() { int i = 1; int *p = new(&i) int(2); *p = 3; }   -  person curiousguy    schedule 13.12.2019
comment
@molbdnilo (а) Предположим, это было. Тогда что?   -  person curiousguy    schedule 13.12.2019
comment
i не является константой, поэтому этот раздел не применяется.   -  person molbdnilo    schedule 13.12.2019
comment
Позвольте нам продолжить это обсуждение в чате.   -  person curiousguy    schedule 13.12.2019
comment
Но как насчет последней части - вы убрали половину из двух предложений, чтобы сделать что-то непреднамеренное. Вся цитата касается только const объектов. Было бы лучше, если бы в вопросе был показан код, который, по вашему мнению, явно исключен.   -  person M.M    schedule 13.12.2019
comment
@ M.M Любой код, создающий объект, исключен.   -  person curiousguy    schedule 13.12.2019
comment
@curiousguy трудно понять, как вы думаете, что абзац о повторном использовании хранилища объекта const означает, что создание любого объекта исключено   -  person M.M    schedule 13.12.2019
comment
@ M.M Как узнать, что объект не был создан в хранилище и т. Д.?   -  person curiousguy    schedule 13.12.2019
comment
вы убрали половину из двух предложений, чтобы сделать что-то непреднамеренное Я пропустил только ту часть, которая не является предметом вопроса. Меня не интересует (на данный момент), что происходит, когда вы повторно используете хранилище статических объектов или хранилище потоков. Проблема здесь исключительно в том, что может произойти, когда вы повторно используете хранилище автоматических объектов: хранилище, которое такой константный объект занимал до окончания его жизненного цикла. Где в памяти эти мертвые объекты?   -  person curiousguy    schedule 13.12.2019
comment
Где эти мертвые объекты в памяти? , мертвого объекта нет нигде как такового. Вы можете завершить время существования константного объекта в автоматическом хранилище (например, с помощью вызова деструктора в рамках ограничений правил), а затем у вас есть хранилище, в котором вы не можете создавать новые объекты. Если вы сделаете это, а затем случайным образом в какой-то другой точке программы попытаетесь создать новый объект в хранилище, то это будет UB, компилятор не сможет отследить это, и это ошибка кодировщика из-за небрежности. На самом деле, когда прицел закончится, это все равно будет UB, так что изначально это была плохая идея.   -  person M.M    schedule 13.12.2019
comment
Думаю, я упустил всю концепцию хранилища и его связь с переменными.   -  person curiousguy    schedule 13.12.2019


Ответы (2)


пользователю необходимо избегать создания любого объекта в любом месте памяти, которое могло быть использовано стеком (для хранения автоматических объектов)

Стандарт не знает о «стеке» и его не волнует - это деталь реализации.

Переменные области блока, не объявленные явно static, thread_local или extern, имеют автоматическую продолжительность хранения.

Хранилище для этих объектов длится до выхода из блока, в котором они были созданы

из [basic.stc.auto]

Таким образом, даже если два объекта окажутся - без перекрывающегося хранилища - по одному и тому же адресу «в стеке», у них все равно будет разное хранилище.

union Lazy {
  Thing thing;
};

// later in a function
{
  Thing const first = /* init */
}
{
  Lazy l;
  new (&l.thing) Thing();
}

first и l.thing, возможно, будут на одном и том же адресе ... но когда создается l.thing, хранилище first уже "пропало".

«Итог»: один и тот же адрес не означает одно и то же хранилище.

person Daniel Jour    schedule 13.12.2019
comment
Итак, компилятор создает хранилище для каждой переменной? - person curiousguy; 13.12.2019
comment
Да ... хранилище обрабатывается автоматически ... что на практике означает, что компилятор генерирует инструкции для управления стеком. Но хранилище - это концепция, определенная стандартом ... то, что создает компилятор, является реализацией этой концепции. - person Daniel Jour; 13.12.2019
comment
Я понимаю, что хранилище стека предоставляется автоматически сгенерированным кодом. Я не знаю, где в std указано, что для каждой локальной переменной создается одно хранилище, или какое-либо четкое объяснение того, когда хранилище создается компилятором. - person curiousguy; 13.12.2019

Storage - абстрактное обозначение ресурса, используемого для хранения объекта в данный момент. Это может быть регистр ЦП, энергозависимая \ энергонезависимая память, кеш и их комбинация.

Это предложение означает только то, что компилятору разрешено предполагать, что такой объект не изменился. Только продолжительность разная. Если static один или глобальный const (который автоматически static) можно предположить, что он никогда не изменится во всей программе, предполагается, что локальные никогда не изменятся во время оценки всех выражений, включающих его в область видимости.

// static duration
const int v1 = 3; // is always 3, variable may or may not exist, 
                  // depending on type and whether address was requested.
                  // for linkage purposes considered static

void foo(int a)
{
  // static duration
  static const int v2 = 3; // initialized with 3  on first call of foo()

  // automatic storage duration
  const int v3 = a + v;    // initialized every time, 
                           //  is just a local variable that cannot be changed
                           //  storage stops to exist after foo() returned
}

Это означает, что память, занятая v3, никогда не сохраняется за пределами выполнения foo(), и ее адрес - указатель или ссылка - не должен возвращаться или иным образом использоваться вне области видимости (например, путем захвата). Вне foo() любой другой объект может быть помещен в то же «хранилище», но это не то же хранилище с абстрактной точки зрения, используемое стандартом. Это не относится к ресурсам, выделенным автоматическим константным объектом, например они могут быть перемещены в другой объект.

person Swift - Friday Pie    schedule 13.12.2019
comment
Итак, каждая переменная получает свое собственное хранилище, а хранилище исчезает после этого? Таким образом, компилятор никогда не использует память повторно. Я понял. - person curiousguy; 13.12.2019
comment
@curiousguy абстрактно, да. На самом деле хранилище - это визитная карточка памяти компилятора при создании кода. Для статических константных объектов, которые могут быть адресом или реальным значением, в последнем случае компилятор сохранит их в инструкции CPu. Слишком большие объекты будут жестко закодированы в двоичное изображение, и будет использоваться их адрес. Компилятор даже может склеить некоторые значения, например записать 64-битное значение вместо 4-х 16-битных. Для автоматических это может быть инструкция ЦП или регистр, но значение, скорее всего, в любом случае будет исходить из инструкции ЦП. - person Swift - Friday Pie; 13.12.2019