Что означают «статически связанные» и «динамически связанные»?

230

Я часто слышу термины «статически связанные» и «динамически связанные», часто в отношении кода, написанного на C , C ++ или C # . О чем они, что именно они говорят и что они связывают?

UnkwnTech
источник

Ответы:

446

Есть (в большинстве случаев, дисконтирование интерпретируемого кода) два этапа перехода от исходного кода (что вы пишете) к исполняемому коду (то, что вы запускаете).

Первый - это компиляция, которая превращает исходный код в объектные модули.

Второе, связывание, это то, что объединяет объектные модули вместе, чтобы сформировать исполняемый файл.

Различие проводится, среди прочего, для включения сторонних библиотек в ваш исполняемый файл без просмотра их исходного кода (например, библиотек для доступа к базе данных, сетевых коммуникаций и графических пользовательских интерфейсов) или для компиляции кода на разных языках ( C и код сборки, например), а затем связать их все вместе.

Когда вы статически связываете файл с исполняемым файлом, содержимое этого файла включается во время компоновки. Другими словами, содержимое файла физически вставляется в исполняемый файл, который вы запустите.

При динамическом связывании указатель на файл, на который выполняется ссылка (например, имя файла файла), включается в исполняемый файл, а содержимое указанного файла не включается во время ссылки. Только когда вы позже запустите исполняемый файл, эти динамически связанные файлы будут куплены, и они будут куплены только в копии исполняемого файла в памяти, а не на диске.

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

Статически связанные файлы «привязаны» к исполняемому файлу во время соединения, поэтому они никогда не меняются. Динамически связанный файл, на который ссылается исполняемый файл, можно изменить, просто заменив файл на диске.

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

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


В качестве примера давайте рассмотрим случай, когда пользователь компилирует свой main.cфайл для статического и динамического связывания.

Phase     Static                    Dynamic
--------  ----------------------    ------------------------
          +---------+               +---------+
          | main.c  |               | main.c  |
          +---------+               +---------+
Compile........|.........................|...................
          +---------+ +---------+   +---------+ +--------+
          | main.o  | | crtlib  |   | main.o  | | crtimp |
          +---------+ +---------+   +---------+ +--------+
Link...........|..........|..............|...........|.......
               |          |              +-----------+
               |          |              |
          +---------+     |         +---------+ +--------+
          |  main   |-----+         |  main   | | crtdll |
          +---------+               +---------+ +--------+
Load/Run.......|.........................|..........|........
          +---------+               +---------+     |
          | main in |               | main in |-----+
          | memory  |               | memory  |
          +---------+               +---------+

В статическом случае вы можете видеть, что основная программа и библиотека времени выполнения C связаны друг с другом во время соединения (разработчиками). Поскольку пользователь обычно не может повторно связать исполняемый файл, он застрял в поведении библиотеки.

В динамическом случае основная программа связана с библиотекой импорта среды выполнения C (то, что объявляет, что находится в динамической библиотеке, но фактически не определяет ее). Это позволяет компоновщику ссылаться, даже если сам код отсутствует.

Затем во время выполнения загрузчик операционной системы выполняет позднее связывание основной программы с DLL-библиотекой времени выполнения C (динамическая библиотека или разделяемая библиотека или другая номенклатура).

Владелец среды выполнения C может в любой момент добавить новую DLL-библиотеку для предоставления обновлений или исправлений ошибок. Как указывалось ранее, это имеет как преимущества, так и недостатки.

paxdiablo
источник
11
Пожалуйста, исправьте меня, если я ошибаюсь, но в Windows программное обеспечение, как правило, включает в себя свои собственные библиотеки при установке, даже если они динамически связаны. Во многих системах Linux с менеджером пакетов многие динамически связанные библиотеки («общие объекты») фактически совместно используются программным обеспечением.
Пол Фишер
6
@PaulF: такие вещи, как общие элементы управления Windows, DirectX, .NET и т. Д., Поставляются с приложениями, в то время как в Linux вы склонны использовать apt, yum или что-то подобное для управления зависимостями - так что вы правы в этом смысле , Приложения Win, которые поставляют свой собственный код в виде библиотек DLL, обычно не разделяют их.
paxdiablo
32
В девятом круге ада зарезервировано специальное место для тех, кто обновляет свои библиотеки DLL и нарушает обратную совместимость. Да, если интерфейсы исчезнут или будут изменены, динамическая компоновка окажется в куче. Вот почему это не должно быть сделано. Во что бы то ни стало добавьте функцию 2 () в вашу DLL, но не меняйте функцию (), если люди ее используют. Лучший способ справиться с этим - перекодировать функцию () таким образом, чтобы она вызывала функцию 2 (), но не меняла сигнатуру функции ().
paxdiablo
1
@Paul Fisher, я знаю, что уже поздно, но ... библиотека, которая поставляется с Windows DLL, не является полной библиотекой, это просто набор заглушек, которые сообщают компоновщику, что содержит DLL. Затем компоновщик может автоматически поместить информацию в .exe для загрузки DLL, и символы не отображаются как неопределенные.
Марк Рэнсом
1
@Santropedro, вы правы во всех смыслах относительно значений имен lib, import и DLL. Суффикс является условным только для того, чтобы не вдаваться в подробности (например, DLL может иметь расширение .dllили .soрасширение) - думайте, что ответ объясняет концепции, а не является точным описанием. И, согласно тексту, это пример, показывающий статическое и динамическое связывание только для файлов времени выполнения C, так что да, это то, что `crt указывает во всех них.
paxdiablo
221

Я думаю, что хороший ответ на этот вопрос должен объяснить , что связывание является .

Когда вы компилируете некоторый код C (например), он переводится на машинный язык. Просто последовательность байтов, которая при запуске заставляет процессор складывать, вычитать, сравнивать, «переходить», читать память, записывать память и все такое. Этот материал хранится в объектных (.o) файлах.

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

Теперь в первые дни программистам приходилось указывать адрес памяти, на котором были расположены эти подпрограммы. Нечто подобное CALL 0x5A62. Это было утомительно и проблематично, если эти адреса памяти нужно когда-либо менять.

Итак, процесс был автоматизирован. Вы пишете программу, которая вызывает printf(), а компилятор не знает адрес памяти printf. Таким образом, компилятор просто пишет CALL 0x0000и добавляет примечание к объектному файлу, в котором говорится «необходимо заменить этот 0x0000 на место в памяти printf ».

Статическая связь означает, что программа компоновщика (GNU-программа называется ld ) добавляет printfмашинный код непосредственно в ваш исполняемый файл и заменяет адрес 0x0000 на адрес printf. Это происходит, когда ваш исполняемый файл создан.

Динамическая связь означает, что вышеупомянутый шаг не происходит. В исполняемом файле все еще есть примечание, которое гласит: «должен заменить 0x000 на место в памяти printf». Загрузчик операционной системы должен найти код printf, загрузить его в память и исправить адрес CALL при каждом запуске программы .

Для программ характерно вызывать некоторые функции, которые будут статически связаны (стандартные библиотечные функции, как printfправило, статически связаны) и другие функции, которые динамически связаны. Статические «становятся частью» исполняемого файла, а динамические «включаются» при запуске исполняемого файла.

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

Artelius
источник
4
Я тоже, но я могу выбрать только 1 ответ.
UnkwnTech
1
Артелиус, я немного разбираюсь в вашем объяснении того, как работают эти сумасшедшие вещи низкого уровня. Пожалуйста, ответьте, какие книги мы должны прочитать, чтобы получить углубленные знания о вышеупомянутых вещах. Спасибо.
Махеш
1
Извините, я не могу предложить какие-либо книги. Вы должны сначала выучить ассемблер. Тогда Википедия может дать достойный обзор таких тем. Вы можете посмотреть ldдокументацию по GNU .
Артелиус
31

Статически связанные библиотеки связаны во время компиляции. Динамически связанные библиотеки загружаются во время выполнения. Статическое связывание добавляет бит библиотеки в ваш исполняемый файл. Динамическое связывание только печет в ссылке на библиотеку; биты для динамической библиотеки существуют в другом месте и могут быть заменены позже.

Джон Д. Кук
источник
16

Потому что ни один из вышеперечисленных постов на самом деле не показывает, как статически связать что-то и увидеть, что вы сделали это правильно, поэтому я решу эту проблему:

Простая программа на C

#include <stdio.h>

int main(void)
{
    printf("This is a string\n");
    return 0;
}

Динамически связать программу C

gcc simpleprog.c -o simpleprog

И запустить fileна двоичном:

file simpleprog 

И это покажет, что оно динамически связано с чем-то вроде:

«simpleprog: исполняемый 64-разрядный LSB ELF, x86-64, версия 1 (SYSV), динамически связанный (использует разделяемые библиотеки), для GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, не удален»

Вместо этого давайте на этот раз статически связать программу:

gcc simpleprog.c -static -o simpleprog

Запуск файла в этом статически связанном двоичном файле покажет:

file simpleprog 

«simpleprog: исполняемый 64-разрядный LSB ELF, x86-64, версия 1 (GNU / Linux), статически связанный, для GNU / Linux 2.6.26, BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b, не удален»

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

К счастью, многие встроенные библиотеки C, такие как muslпредлагают статические возможности связывания почти для всех, если не для всех своих библиотек.

Теперь straceдвоичный файл, который вы создали, и вы можете видеть, что нет никаких библиотек, к которым обращались до начала программы:

strace ./simpleprog

Теперь сравните с выводом straceдинамически связанной программы, и вы увидите, что в статически связанной версии strace намного короче!


источник
2

(Я не знаю C #, но интересно иметь концепцию статического связывания для языка ВМ)

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

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

artificialidiot
источник