Development

В линкере нет никакой магии

(В оригинале – The Linker Is not a Magical Program)

Удручающе часто (в последний раз – буквально перед написанием этой статьи) программисты представляют процесс преобразования исходного кода в исполняемый файл как-то вот так:

1. Написать (или исправить) исходный код
2. Скомпилировать исходный код в объектные файлы
3. Происходит какое-то чудо
4. Появляется исполняемый файл

На шаге 3, конечно же, происходит линковка. Что же меня в этом так раздражает? Я работал в техподдержке десятилетиями, и постоянно получаю вопросы вроде:

1. Линкер сообщает что переменная определена более одного раза
2. Линкер сообщает что переменная является неразрешимым символом 3. Почему исполняемый файл такой большой.

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

Линкер – на удивление простая и незатейливая программа. Все, что она делает – это соединяет вместе секции кода и данных из объектных файлов, связывает символьные ссылки с их определениями, выбрасывает неиспользуемые символы из библиотеки и записывает исполняемый файл. И все. Никакой магии. Самая большая и сложная часть – это декодирование и генерирование ужасно переусложненных форматов файлов, что, однако, не меняет самой сущности линкера.

Пусть, например, линкер сообщает, что переменная определена более одного раза. Многие языки имеют как декларации, так и определения. Декларации обычно помещаются в заголовочные файлы:

extern int iii;

Таким образом создаются внешние ссылки на символ iii. Определение же создает само хранилище для этого символа, появляясь обычно непосредственно в коде, и выглядит примерно вот так:

int iii = 3;

И таких определений может быть только одно. А что, если определение появится в более чем одном файле?

// File a.c 
int iii = 3;

// File b.c
double iii(int x) { return 3.7; }

Линкер сообщит о том, что символ iii определен более одного раза. Если же iii встречается только в виде деклараций, то линкер сообщит о том, что iii является неразрешенной ссылкой. Чтобы определить, почему размер исполняемого файла такой, каким получился, нужно взглянуть на файл .map, обычно генерируемый линкером. Этот файл – ничего более, чем список всех символов в исполняемом файле вместе с их адресами в памяти. Это покажет вам, какие модули были добавлены, и размеры каждого модуля.

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

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

Автор оригинала – Walter Bright