Выделение памяти и конструктор - PullRequest
4 голосов
/ 05 мая 2020

Прошу прощения, если об этом спрашивали раньше или это явно сказано в стандарте, но я не могу определить, выделяется ли память для объектов с автоматическим c хранилищем в начале включающего блока или непосредственно перед выполнением конструктор?

Я спрашиваю об этом, потому что https://en.cppreference.com/w/cpp/language/storage_duration говорит об этом.

Срок хранения Все объекты в программе имеют одну из следующих длительностей хранения:

автомат c продолжительность хранения. Хранилище для объекта выделяется в начале включающего блока кода и освобождается в конце. Все локальные объекты имеют этот срок хранения, кроме объявленных stati c, extern или thread_local.

Значит ли это, что пространство для хранения выделяется даже там, где конструктор по какой-то причине не вызывается?

Например, у меня есть что-то подобное.

{
     if(somecondition1) throw something;
     MyHugeObject o{};
     /// do something
}

Итак, есть вероятность, что MyHugeObject не нужно создавать, но, согласно цитируемому мною источнику, память для него по-прежнему выделяется, несмотря на то, что объект может никогда не быть создан. Так ли это на самом деле или это связано с реализацией?

Ответы [ 3 ]

5 голосов
/ 05 мая 2020

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

Автоматически c storage обычно означает «в стеке вызовов». Т.е. распределение происходит путем уменьшения указателя стека, а освобождение происходит путем его повторного увеличения. Компилятор может выдать код, который выполняет корректировку указателя стека именно там, где время жизни объекта начинается / заканчивается, но это неэффективно: он загромождает сгенерированный код двумя дополнительными инструкциями для каждого используемого объекта. Это особенно проблема с объектами, которые созданы в al oop: указатель стека будет постоянно прыгать вперед и назад между двумя или более позициями.

Для повышения эффективности компиляторы объединяют все возможные распределения объектов в одну single выделение кадра стека : компилятор назначает смещение каждой переменной в функции, определяет макс. size, который требуется для хранения всех переменных, которые присутствуют в функции, и выделяет всю память с помощью одной инструкции уменьшения указателя стека в начале выполнения функции. Тогда очистка - это соответствующее приращение указателя стека. Это устраняет любые накладные расходы на выделение / освобождение из циклов, поскольку переменные в следующей итерации будут просто повторно использовать то же место в кадре стека, что и в предыдущей итерации. Это важная оптимизация, так как многие циклы объявляют по крайней мере одну переменную.

Стандарт C ++ не заботится. Поскольку использование хранилища вне времени существования объекта является UB, компилятор может делать с хранилищем все, что ему заблагорассудится. Программистам тоже не нужно заботиться о времени выполнения своих программ. И это то, что большинство компиляторов оптимизируют, используя выделение фреймов стека.

2 голосов
/ 05 мая 2020

Момент освобождения памяти из системы зависит от реализации. Единственное, что предписано стандартом, - это момент, когда вызывается конструктор и когда объект может быть безопасно использован. time выделить весь кадр в начале блока c и вставить его в конец блока c. Даже если операции со стеком выполняются быстро, их количество проще ограничить, а чем проще, тем надежнее. не говоря уже о моменте, когда кадры выделяются на и извлекаются из этого стека.

2 голосов
/ 05 мая 2020

В стандарте C ++ в [basi c .stc] говорится следующее:

2 Stati c, thread и automati c продолжительность хранения связана с объектами, введенными декларациями (6.1) и неявно созданными реализацией (6.6.7).

Эта 6.6.7 ссылка относится к [class. Contemporary] , посвященный временным. Временные объекты - это не совсем то же самое, но в этом разделе говорится следующее:

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

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

Обратите внимание, что это действительно так. не применяется, когда объект инициализируется - это происходит, когда выполняется оператор объявления, согласно [stmt.dcl] :

2 Переменные с автоматическим c продолжительностью хранения (6.6.5.3) инициализируются каждый раз, когда выполняется их объявление-оператор . Переменные с автоматической c продолжительностью хранения, объявленной в блоке, уничтожаются при выходе из блока (8.6).

Ссылка cppreference , которую вы упомянули, вероятно, обсуждает типичную реализацию, где объекты с автоматическим c сроком хранения размещаются в стеке. В таких реализациях имеет смысл выделять память в начале охватывающего блока (в конце концов, это всего лишь простое (в / де) создание указателя стека, и их группировка полезна).

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

{
    if(somecondition1) throw something;
    {
        MyHugeObject o{};
        /// do something
    }
}

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

...