Выделение памяти ( C++) Время компиляции / время выполнения?

Я не уверен, насколько уместен этот вопрос, но -

Мне любопытно, как компилятор выделяет память для объекта (выделение памяти) еще до того, как он будет построен (еще до того, как будет вызван даже конструктор!).

Как это происходит с примитивными типами данных?

Это звучит немного наивно, но что именно?

Является ли это полностью процессом во время выполнения, или у него (у компилятора) есть какие-либо планы, как сделать это, чтобы сделать это во время выполнения, что он решает заранее во время компиляции. Понятия не имею вообще!

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

Другими словами, когда происходит выделение памяти для буквально ЛЮБОГО вида переменных, с точки зрения времени, в какой момент? На каком этапе компиляции (или во время выполнения)?

4 ответа

Решение

Распределение памяти всегда происходит во время выполнения. Резервирование памяти для объектов, находящихся в стеке или для статических переменных, происходит во время компиляции (или во время выполнения для VLA C99).

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

Существует (в общих чертах) три типичных сценария: распределение в стеке, выделение из кучи и статическое выделение.

Первый - это то, что происходит всякий раз, когда вы объявляете локальную переменную внутри функции:

void foo ( )
{
   int bar = 42;
}

Здесь память для bar размещается в стеке. Выделено в то время foo называется.

Второй сценарий происходит, когда вы создаете экземпляр класса с new оператор:

void foo ( )
{
    MyClass* bar = new MyClass( );
}

Здесь память для i выделяется в куче. Это снова происходит во время выполнения и происходит как new Оператор выполняет. Он работает по существу так же, как C mallocЕсли вы более знакомы с этим.

Наконец, есть статическое распределение.

void foo ( )
{
    static int bar = 42;
}

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

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

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

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

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

Более фундаментальный ответ на этот вопрос заключается в том, что каждый процесс имеет два основных типа памяти, с которыми он работает. «Инструкции» и «Данные». Когда компилятор генерирует программу, он разделяет данные и инструкции и распределяет эти два типа памяти по разным частям программы. Обычно фрагменты разбиваются на куски размером со страницу.

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

Поэтому, когда программа говоритstatic float foo;Компилятор выделяет как минимум sizeof(float) байт памяти, которая становится фактической частью программы. То же самое можно сказать и о глобальных переменных. В больших программах размер данных программы может легко достигать многих мегабайт.

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

Размер данных программы фиксируется при запуске. Но это не означает, что данные программы могут меняться «постоянно». Знаменитый трюк, который используют многие программисты (иногда в качестве хака, иногда для отладки, редко для хорошо написанного программного обеспечения), заключается в создании статической переменной внутри функции, например:

      static int debug=0;
if( debug ) {
     cout << "we have a debug point\n"; 
     debug = 0;
 }
... (some code later)
debug = 1;

Ключевое слово «статический» указывает компилятору сохранить эту переменную в данных программы. Сам код не будет выводить сообщение «у нас есть точка отладки», пока отладка не станет истинной. Это можно использовать для предотвращения выброса выходных данных отладки, обнаружения неожиданных результатов и т. д. Поскольку эти данные являются частью данных программы, они никогда не выходят за пределы области действия, поэтому мы можем полагаться на то, что они сохраняют одно и то же значение каждый раз, когда вызывается функция, в которой находится фрагмент кода.

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

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

Наконец, есть стек. Стек создается загрузчиком и становится частью процесса. Он называется стеком, потому что он буквально распределяет память точно так же, как структура данных стека. Подавляющее большинство временных переменных размещаются в стеке. Он полностью отделен от данных программы и динамически выделяет «кучную» память, но по-прежнему поступает из того же фундаментального пула памяти, что и все остальное. Опять же, стек распределяется частями размером со страницу. В Unix и Linux стек программ может расти неограниченно. Windows не так щедра.

Например:

      int cube( int a )
{
   int cubed = a*a*a;
   return cubed;
}

Здесь у нас есть 3 переменные. Значение, передаваемое в куб, временное и возвращаемое значение. Неудивительно, что все эти значения окажутся в стеке. Стек может представлять собой систему, в которой значения помещаются и извлекаются. В этом случае последовательность будет заключаться в том, чтобы ввести вход «a», затем ввести переменную «cubed», а затем выполнить возврат. Возврат немного отличается тем, что мы извлекаем все, что отправила функция, а затем отправляем результат. Дело здесь не в том, чтобы понять, как управляется стек, а в том, чтобы признать, что все данные находятся в стеке.

Почти каждая программа сводится к этим фундаментальным правилам. Некоторые системы играют в игры, связанные с отсутствием стека. В некоторых системах кучи нет. Но если вы примените вопрос «из какой памяти это произошло» к любой переменной или выделению, вы сможете легко выяснить ее ожидаемое поведение.

Другие вопросы по тегам