Я - программист новичка. Я просто хотел видеть вывод при другой компиляции фаз, собираясь и связываясь. Я не знаю ассемблера также.
Я записал простую программу
#include
int humans = 9;
int main()
{
int lions = 2;
int cubs = populate(lions);
return 0;
}
int populate(int crappyVariable)
{
return ++crappyVariable;
}
Я использовал gcc - S sample.c
Я удивлен выводом ассемблера. Я потерял все имена переменной и имена функций.
это сохранило глобальные идентификаторы как люди, заполните, основной, но это снабдило префиксом их символы нижнего подчеркивания _. Так, я привычка, рассматривая это как использование идентификаторов. Так или иначе точка - это, потерял все идентификаторы.
Мой вопрос состоит в том, как он вызвал бы функции или относился бы к переменным?
Мне действительно любопытно на предмет дальнейших этапов вывода, который был бы в двоичном файле (который не видим).
Как был бы вывод сразу после сборки и перед соединением? Я предполагаю, что это освободит даже снабженные префиксом глобальные идентификаторы подчеркивания также? С другой стороны вопрос состоит в том, как он вызвал бы функции или относился бы к переменным для операций?
Я искал информацию в Интернете, но ничто не мог найти полезным. Может быть я не уверен, что искать. Я не хочу читать большие книги по этому. Но если существуют какие-либо статьи, учебные руководства, которые очищают понятия. Это также было бы полезно.
Я - программист новичка. Так, было бы замечательно, что можно объяснить в простых но технических терминах.
Править: В ответ, к комментарию. Я повредил свой вопрос в несколько вопросов. Вот 2-я часть этого вопроса: не ясный с заданием компоновщика
На уровне базовой машины больше нет имен, только числовые адреса переменных и код. Таким образом, как только ваш код переведен на машинный язык, имена исчезают для практических целей.
Если вы компилируете с опцией "ассемблера" или разобрать код, вы можете увидеть некоторые идентификаторы; они помогут вам найти свой путь по коду, так как от вас не ожидается, что вы будете вычислять данные/смещения кода в вашей голове без необходимости.
Чтобы ответить на ваш вопрос о линковке и тому подобном: Метки и идентификаторы, которые используются только "внутри" программного файла на Си, исчезают после компиляции программы в перемещаемую объектную форму. Однако, внешние имена, такие как main()
необходимы, потому что внешние модули будут ссылаться на них; поэтому скомпилированный объектный файл будет содержать небольшую таблицу с перечислением внешних видимых имён и того, на какое место они ссылаются. Линкер может затем исправить внешние ссылки в вашем модуле от других (и наоборот) на основе этих имён.
После линковки, даже внешне определённые имена больше не нужны. Если вы компилируете с опциями отладки, таблицы имен все равно могут быть прикреплены к конечной программе, так что вы можете использовать эти имена при отладке вашей программы.
.Чтобы посмотреть, как локальные переменные обрабатываются в ассемблерном коде, скомпилируйте что-нибудь вроде:
int main() { int foo = 42; }
Вы заметите не только исчезновение имени переменной, но и , куда идут результирующие данные. Вы увидите нечто вроде:
movq %rsp, %rbp
, которое устанавливает базовый указатель на текущий указатель стека. Затем:
movl $42, -4(%rbp)
Так что это говорит нам о том, что компилятор выделяет некоторое пространство на стеке, но оставляет его без имени. Добавление дополнительных переменных типа foo, в принципе, просто выделит больше памяти под базовым указателем. Переменная, которая была "foo" теперь просто -4(%rbp)
.
Вам действительно нужно прочитать о компиляторах и дизайне компилятора. Начните с http://www.freetechbooks.com/compiler-design-and-construction-f14.html
Вот краткое изложение.
Цель - скопировать в память материал, который будет выполняться и запускаться. Затем операционная система передает управление этим вещам.
Загрузчик копирует материал в память из различных файлов. Эти файлы на самом деле являются своего рода языком, описывающим, куда материал попадает в памяти и что происходит в этих местах. Это своего рода язык "загрузки памяти".
Работа компилятора и компоновщика заключается в создании файлов, которые заставят загрузчика делать правильные вещи.
Вывод компилятора - это "объектные" файлы - по сути, инструкции загрузчика во многих небольших фрагментированных файлах с множеством внешних ссылок. Вывод компилятора - это в идеале какой-то машинный код, в который будут вставляться внешние ссылки. Все внутренние ссылки были разрешены как смещения в памяти кучи, кадры стека или имена функций.
Вывод компоновщика - это большие загрузочные файлы с меньшим количеством внешних ссылок. Во многом это то же самое, что и вывод компилятора в формате. Но у него больше вещей вложено.
Читайте это в команде ld: http://linux.about.com/library/cmd/blcmdl1_ld.htm
Читайте это по команде nm: http://linux.about.com/library/cmd/blcmdl1_nm.htm
Вот некоторые подробности.
"...как он будет вызывать функции или ссылаться на переменные?"
Имена функций, как правило, сохраняются до более поздних стадий вывода.
Имена переменных преобразуются во что-то другое. "Глобальные" переменные выделяются статически, и компилятор имеет карту от имени переменной до типа для смещения в статическую ("кучу") память.
Локальные переменные внутри функции выделяются (обычно) во фрейме стека. Компилятор имеет карту от имени переменной до типа для смещения в рамку стека. При входе в функцию выделяется кадр стека требуемого размера и переменные просто считываются в этот кадр.
"...как бы он вызывал функции или обращался к переменным для выполнения операций?"
Нужно дать подсказку компилятору. Ключевое слово extern
говорит компилятору, что имя в этом модуле не определено, а определено в другом модуле, и ссылка должна быть разрешена во время компоновки (или загрузки).
"...если нечего компоновать..."
Это никогда не бывает правдой. Ваша программа - это только одна часть всего исполняемого файла. Большинство библиотек Си включают основную программу real, которая затем вызывает вашу функцию с именем "main".
" будет ли компоновщик изменять вывод объектного кода ассемблера?"
Это сильно варьируется в зависимости от операционной системы. Во многих ОС компоновщик и загрузка происходят одновременно. Часто случается так, что вывод от компилятора Си выбрасывается в архив без особого разрешения.
Когда исполняемый файл загружается в память, также загружаются ссылки на архив и любые внешние разделяемые объектные файлы.
"Программа не запускается, она просто находится на стадии производства"
Это ничего не значит. Не понимаю, зачем вы это включаете.
"Как карта компоновщика может линковаться в память? Как это будет выглядеть?"
Операционная система выделит блок памяти, в который должна быть скопирована исполняемая программа. Линкер/загрузчик считывает объектный файл, любые объектные архивные файлы и копирует содержимое этих файлов в эту память. Линкер делает копирование и разрешение имен и записывает новый объектный файл, более компилируемый. Загрузчик делает это в реальную память и переворачивает выполнение на результирующую текстовую страницу.
"Время прогона, верно?"
Это единственный способ отладить... время прогона. Это больше ничего не значит, или это не отладка.
Хорошим следующим шагом будет запуск objdump -D на сгенерированном .o и сравнение с .S версией.
Это дает представление о том, что из .S - это макияж, а что транслируется в двоичный файл.
Заключительный этап - связывание, что примерно означает два прохода для разрешения всех меток между несколькими .o файлами по адресам относительно 0 или по адресу загрузки.
Смотрите большую бесплатную книгу по компоновке и загрузке http://www.iecc.com/linker/ для получения дополнительной информации о компоновке
.