Линковка
Компоновщик (также реда́ктор свя́зей, линкер — от англ. link editor, linker ) — программа, которая производит компоновку — принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль.
Для связывания модулей, компоновщик использует таблицы имён, созданные компилятором в каждом из объектных модулей. Такие имена могут быть двух типов:
- Определённые или экспортируемые имена — функции и переменные, определённые в данном модуле и предоставляемые для использования другим модулям
- Неопределённые или импортируемые имена — функции и переменные, на которые ссылается модуль, но не определяет их внутри себя
Работа компоновщика заключается в том, чтобы в каждом модуле разрешить ссылки на неопределённые имена. Для каждого импортируемого имени находится его определение в других модулях, упоминание имени заменяется на его адрес.
См. также
Wikimedia Foundation . 2010 .
Смотреть что такое «Линковка» в других словарях:
- SourceAnalyzer — Тип Статический анализатор кода Разработчик лаборатория BiPro Написана на С++ Операционная система Кроссплатформенное Языки интерфейса английский … Википедия
- GNU Lesser General Public License — Автор Free Software Foundation Версия 3 … Википедия
- Blender — У этого термина существуют и другие значения, см. Blender (журнал) … Википедия
- Make — make утилита, автоматизирующая процесс преобразования файлов из одной формы в другую. Чаще всего это компиляция исходного кода в объектные файлы и последующая компоновка в исполняемые файлы или библиотеки. Утилита использует специальные… … Википедия
- ClanLib — Тип кроссплатформенное игровое SDK Разработчик ClanLib Операционная сист … Википедия
- Двоичный интерфейс приложений — Двоичный (бинарный) интерфейс приложений (англ. Application Binary Interface, англ. ABI) набор соглашений между программами, библиотеками и операционной системой, обеспечивающих взаимодействие этих компонентов на низком уровне на… … Википедия
- Clang — Тип Компилятор Разработчик Apple Написана на … Википедия
- make — В Викисловаре есть статья «make» make утилита, автоматизирующая процесс преобразования файлов из одной формы в другую. Чаще всего это компиляция … Википедия
- Обратная связь: Техподдержка, Реклама на сайте
- Путешествия
Экспорт словарей на сайты, сделанные на PHP,
WordPress, MODx.
- Пометить текст и поделитьсяИскать в этом же словареИскать синонимы
- Искать во всех словарях
- Искать в переводах
- Искать в ИнтернетеИскать в этой же категории
Внутренняя перелинковка сайта
Перелинковка (линковка) сайта – один из самых распространенных и эффективных инструментов внутренней оптимизации, который представляет собой размещение ссылок с одной страницы на другую в пределах одного сайта с целью передачи статического веса.
С точки зрения поисковых машин, отсутствует разница между внутренней и внешней ссылкой на сайт, они точно также передают вес и влияют на позиции в выдачи. Если внутренняя перелинковка выполнена грамотно, то она позволяет не только значительно ускорить процесс продвижения сайта, но и существенно снизить затраты на покупку внешних ссылок.
Схемы перелинковки сайта
Существует множество различных схем перелинковки страниц, которые используются оптимизаторами в своей работе. Выбор схемы зависит от нескольких факторов: типа сайта (визитка, интернет-магазин и т.п.); количество страниц на сайте; структуры сайта (навигация, наличие подкаталогов и т.д.) и др. Рассмотрим пример линковки для типичного сайта, имеющего 3 уровня вложенности страниц:
Если для сайта хорошо подобраны поисковые запросы, и он имеет правильную структуры и навигацию, то существует зависимость между частотой запроса и выбором страницы (уровня вложенности) для его продвижения. Ниже представлено несколько схем перелинковки, которые позволяют передать максимальный статический вес на различные уровни сайта.
Перелинковка под высокочастотные запросы
Для продвижения самых высокочастотных (ВЧ) запросов обычно выбирается главная страница, т.к. она, при прочих равных условиях, обладает максимальным статическим весом. Также не стоит забывать, что максимальное количество естественных ссылок пользователи ставит в виде http://www.site.ru/.
Для передачи максимального веса на главную страницу сайта лучшего всего использовать следующий вид перелинковки:
Перелинковка под среднечастотные запросы
Среднечастотные (СЧ) запросы рекомендуется продвигать на страницах второго уровня вложенности (основные разделы каталогов).
Схема перелинковки, позволяющая передать максимум статического веса, имеет вид:
Перелинковка под низкочастотные запросы
Для продвижения низкочастотных (НЧ) запросов обычно выбирают статьи, которые расположены на третьем уровне вложенности сайта (статьи, описание товаров и т.п.). Если правильно написанные тексты усилить грамотной перелинковкой статей, то рост позиций произойдет без дополнительной покупки ссылок.
Наиболее оптимальная схема перелинковки статей для продвижения НЧ запросов имеет вид:
Все предложенные выше схемы служат для максимальной передачи статического веса и для лучшей эффективности еще необходимо правильно выбрать анкоры ссылок.
Основные правила перелинковки
- ставьте уникальные анкоры в каждой ссылки перелинковки (используйте морфологию, разбавляйте дополнительными словами);
- закрывайте при помощи атрибута rel=»nofollow» внешние ссылки и ссылки, мешающие правильной передачи веса;
- пишите больше уникальных и полезных статей, используйте их в перелинковке. Эффективность напрямую зависит от количества линкованных статей;
- не делайте много ссылок из текста одной статьи (оптимально 2-3);
- при простановке ссылок думайте не только о поисковых машинах, но и о пользователях;
- используйте в перелинковки не только новые статьи, но и старые страницы сайта, которые имеют больший статический вес.
Внутренняя и внешняя линковка в C++
Представляем вам перевод интересной статьи, который подготовили для вас рамках курса «Разработчик C++». Надеемся, что она будет полезна и интересна для вас, как и нашим слушателям.
Сталкивались ли вы когда-нибудь с терминами внутренняя и внешняя связь? Хотите узнать, для чего используется ключевое слово extern, или как объявление чего-то static влияет на глобальную область? Тогда эта статья для вас.
В двух словах
В единицу трансляции включены файл реализации (.c/.cpp) и все его заголовочные файлы (.h/.hpp). Если внутри единицы трансляции у объекта или функции есть внутреннее связывание, то этот символ виден компоновщику только внутри этой единицы трансляции. Если же у объекта или функции есть внешнее связывание, то компоновщик сможет видеть его при обработке других единиц трансляции. Использование ключевого слова static в глобальном пространстве имен дает символу внутреннее связывание. Ключевое слово extern дает внешнее связывание.
Компилятор по умолчанию дает символам следующие связывания:
- Non-const глобальные переменные — внешнее связывание;
- Const глобальные переменные — внутреннее связывание;
- Функции — внешнее связывание.
Основы
Поговорим сначала о двух простых концепциях, необходимых для обсуждения связывания.
- Разница между объявлением и определением;
- Единицы трансляции.
Объявление VS. Определение
Кратко обсудим разницу между объявлением и определением символа: объявление (или декларация) говорит компилятору о существовании конкретного символа, и позволяет обращение к этому символу в случаях не требующих точного адреса памяти или хранилища символа. Определение говорит компилятору, что содержится в теле функции или сколько памяти нужно выделить переменной.
В некоторых ситуациях компилятору недостаточно объявления, например, когда элемент данных класса имеет тип ссылки или значения (то есть не ссылка, и не указатель). В то же время, разрешен указатель на объявленный (но неопределенный) тип, так как ему нужен фиксированный объем памяти (например, 8 байт в 64-битных системах), не зависящий от типа, на который указывает. Чтобы получить значение по этому указателю, потребуется определение. Также для объявления функции нужно объявить (но не определить) все параметры (не важно взятые ли по значению, ссылке или указателю) и возвращаемый тип. Определение типа возвращаемого значения и параметров необходимо только для определения функции.
Разница между определением и объявлением функции весьма очевидна.
int f(); // объявление int f() < return 42; >// определение
С переменными все немного иначе. Объявление и определение обычно не разделяются. Главное, что это:
int x;
Не только объявляет x , но и определяет его. Происходит это благодаря вызову дефолтного конструктора int. (В C++ в отличие от Java, конструктор простых типов (таких как int) по умолчанию не инициализирует значение в 0. В примере выше х будет иметь равен любому мусору, лежащему в адресе памяти, выделенном компилятором).
Но вы можете явно разделить объявление переменной и ее определение при помощи ключевого слова extern .
extern int x; // объявление int x = 42; // определение
Однако, при инициализации и добавлении extern к объявлению, выражение превращается в определение и ключевое слово extern становится бесполезным.
extern int x = 5; // то же самое, что и int x = 5;
Предварительное Объявление
В C++ существует концепция предварительного объявления символа. Это значит, что мы объявляем тип и имя символа для использования в ситуациях, не требующих его определения. Так нам не понадобится включать полное определение символа (обычно — заголовочный файл) без явной необходимости. Тем самым, мы снижаем зависимость от файла, содержащего определение. Главное преимущество — при изменении файла с определением, файл, где мы предварительно объявляем этот символ, не потребует повторной компиляции (а значит, и все прочие файлы его включающие).
Предположим, у нас есть объявление функции (называемое прототипом) для f, принимающее объект типа Class по значению:
// file.hpp void f(Class object);
Сразу включить определение Class — наивно. Но так как мы пока только объявили f , достаточно предоставить компилятору объявление Class . Таким образом, компилятор сможет узнать функцию по ее прототипу, а мы сможем избавиться от зависимости file.hpp от файла, содержащего определение Class , скажем class.hpp:
// file.hpp class Class; void f(Class object);
Допустим, file.hpp содержится в 100 других файлах. И, допустим, мы меняем определение Class в class.hpp. Если вы добавим class.hpp в file.hpp, file.hpp и все 100 содержащих его файла будут должны перекомпилироваться. Благодаря предварительному объявления Class единственными файлами, требующими повторной компиляции, будут class.hpp и file.hpp (если считать, что f определен там).
Частота использования
Важное отличие объявления от определения состоит в том, что символ может быть объявлен много раз, но определен только однажды. Так вы можете предварительно объявить функцию или класс сколько угодно раз, но определение может быть только одно. Это называется Правилом Одного Определения. В C++ работает следующее:
int f(); int f(); int f(); int f(); int f(); int f(); int f()
А это не работает:
int f() < return 6; >int f()
Единицы трансляции
Программисты обычно работают с заголовочными файлами и файлами реализации. Но не компиляторы — они работают с единицами трансляции (translation units, кратко — TU), которые иногда называют единицами компиляции. Определение такой единицы довольно простое — любой файл, переданный компилятору, после его предварительной обработки. Если быть точным, это файл, получаемый в результате работы препроцессора расширяющего макрос, включающего исходный код, который зависит от #ifdef и #ifndef выражений, и копипасты всех файлов #include .
Есть следующие файлы:
#ifndef HEADER_HPP #define HEADER_HPP #define VALUE 5 #ifndef VALUE struct Foo < private: int ryan; >; #endif int strlen(const char* string); #endif /* HEADER_HPP */
#include "header.hpp" int strlen(const char* string)
Препроцессор выдаст следующую единицу трансляции, которая затем передается компилятору:
int strlen(const char* string); int strlen(const char* string)
Обсудив основы, можно приступить к связям. В целом, связь — это видимость символов для компоновщика при обработке файлов. Связь может быть либо внешней, либо внутренней.
Внешняя связь
Когда символ (переменная или функция) обладает внешней связью, он становится видимым компоновщикам из других файлов, то есть “глобально” видимым, доступным всем единицами трансляции. Это значит, что вы должны определить такой символ в конкретном месте одной единицы трансляции, обычно в файле реализации (.c/.cpp), так чтобы у него было только одно видимое определение. Если вы попытаетесь одновременно с объявлением символа выполнить его определение, или поместить определение в файл к объявлению, то рискуете разозлить компоновщик. Попытка добавить файл больше чем в один файл реализации, ведет к добавлению определения больше чем в одну единицу трансляции — ваш компоновщик будет плакать.
Ключевое слово extern в C и C++ (явно) объявляет, что у символа есть внешняя связь.
extern int x; extern void f(const std::string& argument);
Оба символа имеют внешнюю связь. Выше отмечалось, что const глобальные переменные по умолчанию имеют внутреннее связывание, non-const глобальные переменные — внешнее. Это значит, что int x; — то же самое, что и extern int x;, верно? Не совсем. int x; на самом деле аналогичен extern int x<>; (используя синтаксис универсальной/скобочной инициализации, для избежания самого неприятного синтаксического анализа (the most vexing parse)), так как int x; не только объявляет, но и определяет x. Следовательно, не добавить extern к int x; глобально настолько же плохо, как определить переменную при объявлении ее extern:
int x; // то же самое, что и extern int x<>; // скорее всего приведет к ошибке компоновщика. extern int x; // а это только объявляет целочисленную переменную, что нормально
Плохой Пример
Давайте объявим функцию f с внешней связью в file.hpp и там же определим ее:
// file.hpp #ifndef FILE_HPP #define FILE_HPP extern int f(int x); /* . */ int f(int) < return x + 1; >/* . */ #endif /* FILE_HPP */
Обратите внимание, что добавлять здесь extern не нужно, так как все функции явно extern. Разделения объявления и определения тоже не потребуется. Поэтому давайте просто перепишем это следующим образом:
// file.hpp #ifndef FILE_HPP #define FILE_HPP int f(int) < return x + 1; >#endif /* FILE_HPP */
Такой код можно было бы написать до прочтения этой статьи, либо после ее чтения под воздействием алкоголя или тяжелых веществ (например, булочек с корицей).
Давайте посмотрим, почему так делать не стоит. Теперь у нас есть два файла реализации: a.cpp и b.cpp, оба включены в file.hpp:
// a.cpp #include "file.hpp" /* . */
// b.cpp #include "file.hpp" /* . */
Теперь пусть поработает компилятор и сгенерирует две единицы трансляции для двух файлов реализации выше (помните что #include буквально означает копировать/вставить):
// TU A, from a.cpp int f(int) < return x + 1; >/* . */
// TU B, from b.cpp int f(int) < return x + 1; >/* . */
На этом этапе вмешивается компоновщик (связывание происходит после компиляции). Компоновщик берет символ f и ищет определение. Сегодня ему повезло, он находит аж два! Одно в единице трансляции A, другое в B. Компоновщик замирает от счастья и говорит вам примерно следующее:
duplicate symbol __Z1fv in: /path/to/a.o /path/to/b.o
Компоновщик находит два определения для одного символа f . Поскольку у f есть внешнее связывание, он виден компоновщику при обработке и A, и B. Очевидно, это нарушает Правило Одного Определения и вызывает ошибку. Точнее это вызывает ошибку повторяющегося символа (duplicate symbol error), которую вы будете получать не реже, чем ошибку неопределенного символа (undefined symbol error), возникающую, когда вы объявили символ, но забыли определить.
Использование
Стандартным примером объявления переменных extern являются глобальные переменные. Предположим, вы работаете над самовыпекаемым тортом. Наверняка есть глобальные переменные, связанные с тортом, которые должны быть доступны в разных частях вашей программы. Допустим, тактовая частота съедобной схемы внутри вашего торта. Это значение естественно требуется в разных частях для синхронной работы всей шоколадной электроники. (Злой) C-способ объявления такой глобальной переменной имеет вид макроса:
#define CLK 1000000
Программист C++, испытывающий к макросам отвращение, лучше напишет настоящий код. Например такой:
// global.hpp namespace Global < extern unsigned int clock_rate; >// global.cpp namespace Global
(Современный программист C++ захочет использовать разделительные литералы: unsigned int clock_rate = 1’000’000;)
Внутренняя Связь
Если у символа есть внутренняя связь, то он будет виден только внутри текущей единицы трансляции. Не путайте видимость с правами доступа, например private. Видимость означает, что компоновщик сможет использовать этот символ только при обработке единицы трансляции, в которой был объявлен символ, а не позже (как в случае символов с внешней связью). На практике, это значит, что при объявлении символа с внутренней связью в заголовочном файле, каждая единица трансляции, включающая в себя этот файл, получит уникальную копию этого символа. Как если бы вы предопределили каждый такой символ в каждой единице трансляции. Для объектов это значит, что компилятор будет буквально выделять совершенно новую, уникальную копию для каждой единицы трансляции, что, очевидно, может привести к высоким расходам памяти.
Для объявления символа с внутренней связью, в C и C++ существует ключевое слово static. Такое использование отличается от применения static в классах и функциях (или, в целом, в любых блоках).
static int variable = 42;
void function1();
void function2();
Линковка в Linux
Линковка это процесс компоновки различных кусков кода и данных вместе, в результате чего получается один исполняемый файл. Линковка может быть выполнена во время компиляции, во время загрузки (загрузчиком) и также во время исполнения (исполняемой программой). Раньше (конец 40-х) линковка выполнялась вручную, сейчас мы имеем программы линковщики (linkers), которые дают возможность динамической линковки разделяемых библиотек (shared libraries).
3 Основы
Пусть у нас есть два файла с кодом a.c и b.c. Чтобы скомпилировать эти два файла при помощи GCC, мы вызываем следующий код
gcc a.c b.c
Это вызывает следующую последовательность:
- Запустить препроцессор на файле a.c и сохранить результат в промежуточный файл a.i
cpp other-command-line options a.c /tmp/a.i
cc1 other-command-line options /tmp/a.i -o /tmp/a.s
as other-command-line options /tmp/a.s -o /tmp/a.o
ld other-command-line-options /tmp/a.o /tmp/b.o -o a.out
После этого мы можем запустить наш бинарный файл ./a.out. Оболочка командной строки вызовет функцию загрузчика, которая скопирует код и данные из исполняемого файла a.out в память, затем передаст управление в начало программы. Функция загрузчик называется execve, она загружает код и данные исполняемых объектных файлов в память, затем запускает их выполнение, прыгая на первую инструкцию.
4 Линковщики и Загрузчики
Линковщики (linkers) и загрузчики (loaders) выполняют концептуально разные, но в целом похожие задачи:
- Загрузка программ. Копирование образа программы с жёсткого диска в RAM. В некоторых случаях загрузка программы (loading) также может включать выделение дисковой памяти или отображение виртульного адресного пространства на дисковое пространство.
- Релокация (relocation). Компиляторы и ассемблеры генерируют объектный код для каждого входного модуля программы с началом адресации в нуле. Релокация — это процесс изменения адреса загрузки различных частей программы во время объединения всех секций одного типа в одну секцию. Секции кода и данных таким образом будут указывать на корректные адреса в рантайме.
- Symbol Resolution. Программы имеют внутри себя множество подпрограмм; указание одной подпрограммы на другую подпрограмму происходит через символьные таблицы. Работа линковщика — подменять указания на символ подпрограммы на указание адреса расположения подпрограммы, изменяя объектный код.
В итоге, получается что загрузчик выполняет загрузку программ; линковщик выполняет symbol resolution; оба выполняют релокацию.
5 Объектные файлы
- Перемещаемый объектный файл (relocatable object file) — содержит бинарный код и данные в форме, которая может быть скомпонована с другими перемещаемыми объектными файлами во время компиляции. В итоге получаем исполняемый объектный файл, скомпонованный из перемещаемых объектный файлов.
- Исполняемый объектный файл (executable object file) — содержат бинарный код и данные в форме, которая может быть напрямую загружена в память и выполнена.
- Разделяемый объектный файл (shared object file) — специальный тип перемещаемого объектного файла, который может быть загружен в память и слинкован динамически либо во время загрузки в память, либо во время выполнения.
Компиляторы и ассемблеры генерируют перемещаемые объектные файлы (а так же разделяемые объектные файлы). Линковщики компонуют эти объектные файлы вместе и генерируют исполняемые объектные файлы.
6 ELF
Объектные файлы разнятся в разных ОС. Первые UNIX системы использовали формат a.out. Ранние System V использовали формат COFF (common object file format). Windows NT использует разновидность формата COFF, называемую PE (portable executable); IBM использует собственный формат IBM 360. Современные UNIX системы, такие как Linux и Solaris используют формат UNIX ELF (executable and linking format).
6.1 Заголовки Elf
.text the machine code of the compiled program. .rodata read-only data, such as the format strings in printf statements. .data initialized global variables .bss uninitialized global variables. BSS (начало блока данных — block storage start), эта секция обычно пустует в объектных файлах; этакая заглушка. .symtab таблица символов, содержащая информацию о функциях и глобальных переменных, определённых и адресованных в коде программы. Эта таблица не содержит записей о локальных переменных, эта информация содержится на стеке. .rel.text список мест в секции .text, которые необходимо модифицировать, когда линковщик будет компоновать этот объект с другими объектными файлами. .rel.data информация о релокации глобальных переменных, которые объявлены, но не определены в текущем модуле программы. .debug таблица отладочных символов с записями о локальных и глобальных переменных. Эта секция будет присутствовать только если компилятору был передан флаг компиляции с таблицей отладочных символов (-g для gcc). .line отображение номеров строк в исходном C-файле и машинными кодами инструкций. Эта информация необходима для отладки программ. .strtab таблица строк для таблицы символов .symtab и секции .debug
7 Символы и адресация символов
Каждый перемещаемый объектный файл содержит таблицу символов связанные символы. В контексте линковщика представлены следующие виды символов:
- Глобальные символы объявленые на уровне модуля — могут быть адресованы из других модулей.Все не-статические и глобальные переменные попадают в эту категорию.
- Глобальные символы адресованные в коде, но объевленные где-то вне. Все функции и переменные с модификатором extern попадают в эту категорию.
- Локальные символы объявленные и адресованные исключительно во входном модуле. Все статические функции и статические переменные попадают в эту категорию.
Линковщик разрещает адресацию символов путём соотношения каждой ссылки на символ только к одному определению символу из таблицы символов.
8 Линковка статических библиотек
Статические библиотеки это коллекция конкатенированных объектных файлов схожего типа. Эти библиотеки хранятся на диске в архиве. Архив также содержит мета-информацию для ускорения поиска в нём. Каждый архив с ELF начинается с магической последовательности !\n. Статические библиотеки передаются на вход линковщику, который копирует только объектные модули, упоминаемые в программе. В процессе разрешения адресации символов при работе со статическими библиотеками линковщик сканирует перемещаемые объектные файлы и архивы справа-налево в порядке указания аргументов вызова. В процессе сканирования линковщик создаёт набор O-файлов (перемащаемых объектных файлов, которые будут включены в исполняемый файл); набор U-файлов (неразрешённых пока символов); набор D-файлов (символы, объявленные в предыдущих модулях). Изначально все три набора пустые.
- На каждый следующий входной аргумент линковщик определяет передаётся ли объектный файл или архив. Если это перемещаемый объектный файл, то линковщик добавляет его в набор O, обновляет наборы U и D и переходит к следующему входному аргументу
- Если входной аргумент архив, линковщик сканирует список членов модулей, входящих в архив, чтобы отыскать любые неразрешённые символы, находящиеся в наборе U. Если такие символы находятся, то они добавляются в список O и обновляется список U. Список D дополняется символами, найденными в архиве.
- Когда все входные аргументы пройдены, но если набор U не пуст, то линковщик сообщает об ошибке линковки и завершает свою работу. Иначе, если набор U пуст, линковщик компонует и релоцирует объектные файлы из набора O и генерирует финальный исполняемый файл.
9 Релокация
После того как линковщик разрешил адресацию всех символов, каждый адресация символа ссылается ровно на одно определение символа. В этот момент линковщик запускает процесс релокации, состоящий из двух шагов:
- Релокация секций и определения символов. Линковщик объединяет все секции одного типа в новую секцию. К примеру, линковщик объединяет все секции .data всех входных перемещаемых объектов в новую секцию .data результирующего исполняемого файла. Похожий процесс происходит для секции .code. Затем линковщик указывает текущий адрес памяти для этой сгенерированной секции. Так для каждой секции и символа. После завершения этого шага каждая инструкция и глобальная переменная в прогармме будет иметь уникальный адрес в момент загрузки.
- Релокация адресации символов внутри секций. На этом шаге линковщик изменяет адресации на символы в коде и секциях данных так, чтобы они указывали на корректный уникальный адрес в момент загрузки.
Ассемблер при релокации создаёт секции .relo.text и .relo.data, в которых содержится информация как разрешить адресацию (адрес для обращения к символу). ELF содержит в секциях релокации следующие данные:
- Смещение (offset). Для перемещаемых файлов значение смещения это смещение в байтах от начала секции до получившегося после релокации адреса.
- Символ (symbol). Индекс символа в символьной таблице.
- Тип (type). Тип релокации.
10 Динамическая линковка: разделяемые библиотеки
Статические библиотеки, описанные выше, имеют существенный недостаток. Например, возьмём стандартные функции printf и scanf. Они используются почти что в каждой программе. Пусть на системе запущено 50-100 процессов, каждый процесс содержит свою копию исполняемого кода printf и scanf — это существенный объём затраченной памяти. Разделяемые библиотеки в свою очередь направлены на исправление этого недостатка статических библиотек. Разделяемые библиотеки это объектные модули, которые могут быть загружены в память в момент исполнения программы и после слинкованы с программой. Разделяемые библиотеки (shared libraries) называют так же разделяемые объекты (shared objects). На большинстве систем UNIX они именуются с суффиксом .so; на системах HP-UX — с суфиксом .sl; на системах Microsoft они называются DLL. Чтобы собрать разделяемый объектный файл, компилятор надо вызывать со специальным флагом
gcc -shared -fPIC -o libfoo.so a.o b.o
Эта команда сообщает компилятору, что надо сгенерировать разделяемую библиотеку libfoo.so, собранную из объектный файлов a.o и b.o. Флаг -fPIC сообщает компилятору, что надо сгенерировать адресо-независимый код (position independent code — PIC). Теперь представим что объектный модуль bar.o зависит от a.o и b.o. В этом случае мы компилируем его так:
gcc bar.o ./libfoo.so
Эта команда создаёт исполняемый файл a.out, который будет линковаться с libfoo.so в момент загрузки. Здесь a.out не содержит в себе объектный модулей a.o и b.o, которые были бы включены в него, если бы мы использовали статическую линковку. Исполняемый файл просто содержит некоторую информацию о релокации и таблицу символов, которые позволяют адресоваться к коду и данным в libfoo.so и эта адресация будет разрешена в процессе исполнения (runtime). Таким образом, a.out это не совсем исполняемый файл, который имеет зависимость от libfoo.so. Исполняемый файл содержит секцию .interp, где содержится имя динамического линковщика (который сам является разделяемым объектом в системах Linux — ld-linux.so). Таким образом, когда исполняемый файл загружается в память, загрузчик передаёт управление динамическому линковщику. Динамический линковщик содержит некоторый код, который отображает пространство адресов динамических библиотек на пространство адресов испольняемой программы.
- Происходит релокация кода и данных из libfoo.so в область памяти
- Происходит релокация адресации в a.out на символы объявленные в libfoo.so.
В конце работы динамический линковщик передаёт контроль исполняемой программе. С этого момента местоположение разделяемого объекта зафиксировано в памяти.
11 Загрузка разделяемой библиотеки из приложения
Разделяемая библиотека может быть загружена из приложения в любой момент выполнения. Приложение может обратиться к динамическому линковщику с просьбой загрузить и прилинковать динамическую библиотеку. Linux, Solaris и другие системы поддерживают различниые функции, которые могут быть использованы для динамической загрузки разделяемых объектов. В Linux это системные вызовы dlopen, dlsym, dlclose, используемые для загрузки разделяемого объекта, поиска символа в разделяемом объекте и для закрытия разделяемого объекта.
12 Утилиты для работы с объектными файлами
ar создаёт статические библиотеки. objdump может быть использована для показа всей информации о бинарном объектном файле. strings показывает все строковые данные в бинарном файле, содержащие печатные символы. nm перечислить символы, определённые в символьной таблице объектного файла. ldd перечислить динамические библиотеки, от которых зависит объектный файл. strip удалить информацию из таблицы символов.
Author: Pavel Vavilin
Created: 2017-10-25 Wed 23:10