Что означает "статически связанное" и "динамически связанное"?

Я часто слышу термины "статически связанные" и "динамически связанные", часто ссылаясь на код, написанный в C, C++ или С#, но я не знаю много все об этом. Что они, о чем они говорят, и что они связывают?

Ответ 1

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

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

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

Различие заключается в том, что, помимо прочего, позволяет включить сторонние библиотеки в ваш исполняемый файл, если вы не видите их исходный код (например, библиотеки для доступа к базе данных, сетевые коммуникации и графические интерфейсы пользователя) или для компиляции кода в разные языки (например, 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 связаны друг с другом во время соединения (разработчиками). Поскольку пользователь обычно не может перенастроить исполняемый файл, они застряли в поведении библиотеки.

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

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

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

Простая программа 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: ELF 64-разрядный исполняемый файл LSB, x86-64, версия 1 (SYSV), динамически связанный (использует общие библиотеки), для GNU/Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, не разделяется"

Вместо этого статично свяжем программу на этот раз:

gcc simpleprog.c -static -o simpleprog

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

file simpleprog 

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

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

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

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

strace ./simpleprog

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

Ответ 5

(Я не знаю С#, но интересно иметь статическую концепцию связывания для языка VM)

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

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