Что происходит с идентификаторами в программе?

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

Я записал простую программу

#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-я часть этого вопроса: не ясный с заданием компоновщика

6
задан Community 23 May 2017 в 12:30
поделиться

4 ответа

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

Если вы компилируете с опцией "ассемблера" или разобрать код, вы можете увидеть некоторые идентификаторы; они помогут вам найти свой путь по коду, так как от вас не ожидается, что вы будете вычислять данные/смещения кода в вашей голове без необходимости.

Чтобы ответить на ваш вопрос о линковке и тому подобном: Метки и идентификаторы, которые используются только "внутри" программного файла на Си, исчезают после компиляции программы в перемещаемую объектную форму. Однако, внешние имена, такие как main() необходимы, потому что внешние модули будут ссылаться на них; поэтому скомпилированный объектный файл будет содержать небольшую таблицу с перечислением внешних видимых имён и того, на какое место они ссылаются. Линкер может затем исправить внешние ссылки в вашем модуле от других (и наоборот) на основе этих имён.

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

.
3
ответ дан 17 December 2019 в 04:47
поделиться

Чтобы посмотреть, как локальные переменные обрабатываются в ассемблерном коде, скомпилируйте что-нибудь вроде:

int main() { int foo = 42; }

Вы заметите не только исчезновение имени переменной, но и , куда идут результирующие данные. Вы увидите нечто вроде:

movq    %rsp, %rbp

, которое устанавливает базовый указатель на текущий указатель стека. Затем:

movl    $42, -4(%rbp)

Так что это говорит нам о том, что компилятор выделяет некоторое пространство на стеке, но оставляет его без имени. Добавление дополнительных переменных типа foo, в принципе, просто выделит больше памяти под базовым указателем. Переменная, которая была "foo" теперь просто -4(%rbp).

.
0
ответ дан 17 December 2019 в 04:47
поделиться

Вам действительно нужно прочитать о компиляторах и дизайне компилятора. Начните с 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".

" будет ли компоновщик изменять вывод объектного кода ассемблера?"

Это сильно варьируется в зависимости от операционной системы. Во многих ОС компоновщик и загрузка происходят одновременно. Часто случается так, что вывод от компилятора Си выбрасывается в архив без особого разрешения.

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

"Программа не запускается, она просто находится на стадии производства"

Это ничего не значит. Не понимаю, зачем вы это включаете.

"Как карта компоновщика может линковаться в память? Как это будет выглядеть?"

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

"Время прогона, верно?"

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

2
ответ дан 17 December 2019 в 04:47
поделиться

Хорошим следующим шагом будет запуск objdump -D на сгенерированном .o и сравнение с .S версией.

Это дает представление о том, что из .S - это макияж, а что транслируется в двоичный файл.

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

Смотрите большую бесплатную книгу по компоновке и загрузке http://www.iecc.com/linker/ для получения дополнительной информации о компоновке

.
0
ответ дан 17 December 2019 в 04:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: