![]() |
|
Модераторы: Daevaorn |
![]()
|
|
| tzirechnoy |
|
|||
|
Эксперт ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1173 Регистрация: 30.1.2009 Репутация: 2 Всего: 16 |
Да всё примерно такжэ, как и для внешних функцый. Кодогенератор генерит код (call ..., jmp ... и т.д.), а вместо адресов пишэт нули (условно, на разных архитектурах там по-разному можэт быть реализовано). И записывает списки мест, куда поставить адрес (обычно в случае Си -- он всё это, и код и списки мест, пишэт в объектный файл. Расшырение .o. В винде, возможно, .obj). Затем линкер берёт этот объектный файл, плюс другие указанные ему библиотеки объектных файлов (в т.ч. т.н. "динамические"), выдирает из них списки адресов символов, строит дерево зависимостей, складывает весь код из объектных файлов (ну, на самом деле только из "статических" объектных файлов, т.е. не находящихся в динамических библиотеках) в один большой объектный код (обычно -- у себя в памяти), ужэ в этом коде вычисляет получившыеся адреса всех переменных и функцый (кроме динамических, которых там нет), и затем проходит по списку мест, в которые он должэн записать эти адреса, и записывает их.
То есть никакой разницы -- в этом объектном файле, не в этом, сначала весь код, потом все адреса. Результат потом записывает в исполняемый файл (в винде -- .exe, так и буду его кстати дальшэ называть, для краткости). Да, две детали: первая -- функцыи и переменные, объявленные static. Для них всё тожэ самое часто происходит ещё до физической записи в объектный файл. То есть после кодогенератора непосредственно в памяти вызывается такой маленький, тупенький линкер, и записывает все получившыеся внутренние адреса (ну, на самом деле, смещения) в оставленные для адресов места. Вторая деталь -- динамические библиотеки. Для них обычный линкер не выстраивает их код в одну линейку (поскольку при запуске это можэт быть совсем другой код), соотвественно, не записывает его в вывод (.exe ужэ). Когда линкер находит, что такие-то символы берутся из динамических библиотек -- он записывает в свой выходной файл список имён внешних функцый, на которые кто-то внутри ссылается. Ну, почти как в случае генерацыи .o-файла. А вот с адресами в основном файле (том самом .exe, который мы собираем) происходит интересная вещь: дело в том, что если просто оставить нули и понадеяться, что при запуске динамический линкер пропишэт вместо них правильные адреса -- то получится, что при одновременном запуске нескольких копий программы придётся держать в памяти столько копий кода, сколько их было запущено -- ведь код у разных экземпляров станет разным и держать его в одной readonly копии не получится. Поэтому для динамических символов составляется отдельная таблица адресов перехода, которая собирается в одном известном месте (ну, в концэ) объектного кода. И непосредственно в код прописываются адреса ячеек этой таблицы. А ужэ при выполнении динамический линкер загружает эту таблицу в память и прописывает в неё правильные адреса функцый из внешних .dll -- благо эта таблица сильно меньшэ, чем весь выполняемые код данного .exe-шника. Более того, сейчас на самом деле там ещё веселее -- чтобы не перелопачивать при загрузке сразу всю таблицу, пытаясь найти в разных .dll-ах тысячи и десятки тысяч имён, которые, возможно, при этом запуске и не понадобятся -- в ячейки этой таблицы записывается адрес "трамплина", стандартной подпрограммки из пары команд, которая вызывается стандартную функцыю, которая по адресу вызванной функцыи определяет, что было вызвано и находит и записывает в соответствующую ячейку таблицы правильный адрес из .dll. Таким образом, ячейки таблицы заполняются правильными адресами когда они первый раз используются. Как-то так. Но, в общем, со своими адресами никаких проблем нет: в первый проход остаются нули, ко второму проходу компиляторы их знают. |
|||
|
||||
| LeonidPr |
|
||||||
|
Бывалый ![]() Профиль Группа: Участник Сообщений: 220 Регистрация: 17.2.2012 Где: г. Чебоксары Репутация: нет Всего: 1 |
Тут много уже понаписали, так что я скорее всего повторюсь. Просто недавно сам столкнулся с похожей проблемой (но в более простом виде). Писал элементарный ассемблер с некоего своего набота мнемоник в свой же байткод (его микроконтроллер потом исполняет). Необходима была поддержка меток в исходном коде (то же что и ваши подпрограммы). Все решилось стандартным (ну... почти стандартным) двухпроходным методом. За первый проход делается разбор строк исходного кода, счетчик инструкций соответственно обновляется после обработки каждой строки на нужное кол-во байт, равное размеру считанной инструкции + размер её операндом. Если встречается метка, то в некий std::map<string, uint32_t> заносится наименование метки и текущий счетчик инструкций, на втором проходе еще раз просматривается исходный код и уже ведется кодогенерация, если у считанной иснструкции операнд - метка, она берется из того map-а и её адрес подставляется в сгенерированный код. возможно вам для начала следует поступить аналогично.
Сначала не заморачиваться, пройти по коду (уже распарсенному, в виде дерева вывода), вычислить адреса меток f1, f2, а потом на этапе кодогенерации подставлять их в аргументы соответствующих call-ов. Только вот как при этом работает оптимизатор - не знаю, мы в свое время в универе не дошли до него, а я как-то не интересовался. --------------------
pkunzip.zip |
||||||
|
|||||||
![]()
|
| Правила форума "С++:Общие вопросы" | |
|
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |