Почему книги говорят: «Компилятор выделяет пространство для переменных в памяти»?

18

Почему книги говорят: «Компилятор выделяет пространство для переменных в памяти». Разве это не исполняемый файл, который делает это? Я имею в виду, например, если я напишу следующую программу,

#include <iostream>
using namespace std;

int main()
{
   int foo;
   return 0;
}

и скомпилировать его, и получить исполняемый файл (пусть это будет program.exe), теперь, если я запустлю program.exe, этот исполняемый файл сам даст команду выделить место для переменной foo. Не так ли? Пожалуйста, объясните, почему книги продолжают говорить: «компилятор сделает это ... сделайте это».

Мирный Кодер
источник
11
о каких книгах ты говоришь?
wirrbel
4
Ваш «связанный вопрос» должен быть отдельным вопросом.
SShaheen
Компилятор генерирует код, который делает то или иное, что они говорят. прямо или косвенно.
old_timer
FYI stackoverflow.com/questions/7372024/… и обратите внимание, что компилятор может решить изменить расположение воспринимаемой переменной в памяти для выравнивания, например: stackoverflow.com/questions/17774276/…
NoChance

Ответы:

20

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

Я предполагаю, что это должно сделать четкое различие между памятью, фактически выделенной вашим собственным кодом. Компилятор вставит некоторый код в вашу программу, который выполняет выделение памяти (например, используя команды new, malloc или аналогичные).

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

Торстен Мюллер
источник
Да, это то, во что я верил. Спасибо за быстрый ответ!
Мирный кодер
12
компилятор выделяет переменную foo в стеке, заменяя ее смещением на указатель стека во время компиляции. Это совсем не связано с распределением кучи, которое выполняется mallocet. и др.
wirrbel
@holger: Вы, конечно, технически верны. Но пространство стека как таковое все еще должно быть выделено, когда программа запускается, прежде чем ее можно будет использовать (что может происходить различными способами, иногда в зависимости от архитектуры ЦП). Я пытался найти некоторые детали, как это происходит, но без особого успеха.
Торстен Мюллер
2
Я думаю, что размер стека для основного потока резервируется компоновщиком, а затем обрабатывается ОС. Для пользовательских потоков это больше похоже на распределение кучи, т.е. вызывающая сторона может фиксировать размер во время выполнения.
wirrbel
4

Это зависит от переменной. ОС выделяет кучу, программа выделяет стек, а компилятор выделяет пространство для глобальных переменных / статики, то есть они встроены в сам исполняемый файл. Если вы выделите 1 МБ глобальной памяти, размер вашего exe увеличится как минимум на 1 МБ.

Джеймс
источник
1
Вопрос не в этом.
Филипп
2
на самом деле это ближе к вопросу, чем другие ответы, перечисленные здесь.
wirrbel
@ Джеймс Ах, это не мой опыт. Например, для int test[256][1024]; int main(){ test[0][0]=2; return 0; } этой маленькой программы выделено 1 МБ, но генерируется только объектный файл объемом 1,4 КБ и исполняемый файл объемом 8,4 КБ. Тем не менее, он должен использовать правильный объем оперативной памяти.
Гарет Клаборн
1
Разве это не должны быть только команды выделения, хранящиеся для глобалов? Если вы жестко закодировали все значения, используя примитивы, такие как int или char, размер исполняемого файла определенно увеличился бы больше, чем количество добавленных переменных. Например, int a1=1,a2=2,... вплоть до ... , a1048576=1048576;Только тогда я определенно получу что-то большее, чем 1 Мб, я думаю.
Гарет Клаборн
2
Это то, что помещает данные в раздел BSS exe
James
4

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

Например, когда вы пишете

int foo;

Вы можете увидеть, что «я говорю компилятору [ в выводе, который он генерирует ] запрос, чтобы компьютер зарезервировал достаточно оперативной памяти для int, на который я могу ссылаться позже, компилятор, вероятно, будет использовать идентификатор ресурса или какой-то механизм для отслеживания foo в машинный код, вы можете использовать foo в текстовом файле вместо написания ассемблера! Ура !

Таким образом, вы также можете посмотреть на это, когда компилятор пишет письмо ( или, возможно, роман / энциклопедию ) всем целевым процессорам и устройствам. Письмо написано в двоичных сигналах, которые (как правило) могут быть переведены на разные процессоры путем изменения цели. Любое «письмо» и / или комбо может отправлять всевозможные запросы и / или данные - например, выделите место для этой переменной, которую использовал программист.

Гарет Клаборн
источник
3

Сказать «компилятор выделяет память» может быть не совсем точным в буквальном смысле, но это метафора, которая наводит на мысль о правильном пути.

Что действительно происходит, так это то, что компилятор создает программу, которая выделяет собственную память. За исключением того, что не программа распределяет память, а ОС.

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

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

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

Майкл Шоу
источник
0

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

Теперь не путайте это с другим динамическим управлением памятью, таким как malloc, new или может быть вашим собственным управлением памятью. Компиляторы имеют дело с хранением и доступом к переменным, но не имеет значения, что означает фактическое значение в другой структуре / библиотеке. Например:

byte* pointer = (byte*)malloc(...);

Во время выполнения malloc может возвращать произвольное число, но компилятору все равно, где его хранить.

Codism
источник
0

Более точная формулировка была бы: - «компилятор говорит загрузчику зарезервировать место для переменных»

В среде C-ish будет три типа пространства для переменных:

  • фиксированный блок для статических переменных
  • Большой блок для «автоматических» переменных, обычно называемый «стеком». Функции захватывают чанк при входе и освобождают его при возврате.
  • Большой блок, называемый «кучей», из которого выделяется память, управляемая программой (с помощью malloc () или аналогичного API управления памятью).

На современной ОС куча памяти фактически не будет зарезервирована, а будет выделена как требуется.

Джеймс Андерсон
источник
0

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

(Если вам интересно, где находятся ваши переменные, вы можете проверить это грязным способом (если вы не хотите проверять структуру файла obj, в любом случае, почему бы и нет?), Так что вы можете объявить некоторые другие виды переменных: константа, статические, динамические, malloc()-распределенные и т. д., и отображать их адреса (используйте %Xформаттер printf()для лучшей читаемости). Переменные, находящиеся в стеке, будут иметь очень разные адреса памяти.)

ern0
источник
0

Единственное, что можно сделать во время выполнения, это увеличить указатель стека на определенную величину. Поэтому компилятор решает заранее:

  • сколько места в стеке потребуется для функции.
  • При каком смещении от указателя стека будет находиться каждая отдельная переменная.

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

Инго
источник