Avatar ·

Когда использовать динамические, а когда статические библиотеки в C++?

Всем привет! Я только начинаю разбираться с C++ и сборкой проектов, и у меня возникла путаница с библиотеками. Вроде бы понял, что есть статические (.lib, .a) и динамические (.dll, .so) библиотеки, но не могу понять, когда что применять на практике. Например, я пишу небольшой проект — консольный калькулятор. Я использую библиотеку для работы с математическими функциями (допустим, свою же). Если я подключу её статически, то код просто скопируется в мой .exe. Если динамически — то библиотека будет отдельным файлом. В чём профит? В интернете пишут, что статические библиотеки делают exe больше, а динамические — меньше, но это же не единственное различие. Ещё я слышал, что DLL могут "ломаться" при обновлении, а статические — нет. Но зачем тогда вообще нужны динамические, если они ненадёжные? И как правильно линковать сторонние библиотеки, например, Boost или OpenGL? Мне нужно решение, которое будет работать и на Windows, и на Linux. Помогите, пожалуйста, разобраться. Приведите примеры кода, как подключать оба типа библиотек в CMake или в Visual Studio. Заранее спасибо! <<>>

Основные различия между статическими и динамическими библиотеками

Вы абсолютно правы: главное различие — в моменте компоновки и способе распространения. Давайте разберём это на пальцах.

  • Статическая библиотека (.lib в Windows, .a в Linux) — это архив объектных файлов. При сборке линковщик копирует нужные части библиотеки прямо в исполняемый файл. Ваш .exe становится самодостаточным.
  • Динамическая библиотека (.dll в Windows, .so в Linux) — это отдельный файл, который загружается в память во время выполнения. Исполняемый файл содержит только ссылки (импорт) на функции из DLL.

Когда использовать статические библиотеки

Статическая линковка — это консервативный и надёжный подход. Вот реальные сценарии:

  • Утилиты командной строки (ваш калькулятор). Пользователь скачал один .exe и работает. Не нужно думать о зависимостях.
  • Встраиваемые системы или изолированные окружения, где нет менеджера пакетов.
  • Закрытые коммерческие продукты, где вы не хотите, чтобы пользователь мог легко заменить вашу DLL на чужую.
  • Маленькие проекты, где размер не критичен, а надёжность важнее.

Пример подключения статической библиотеки в CMake:

# CMakeLists.txt
add_library(my_math STATIC math.cpp math.h)  # собираем статическую библиотеку

add_executable(calculator main.cpp)
target_link_libraries(calculator PRIVATE my_math)  # линкуем статически

Когда использовать динамические библиотеки

Динамическая линковка незаменима в больших проектах:

  • Плагины и модули. Например, Photoshop или браузер загружают кодеки и расширения как DLL. Вы можете добавить новый функционал без перекомпиляции всей программы.
  • Обновление по воздуху. Если нашли баг в библиотеке — просто замените .dll/.so. Пользователю не нужно качать весь exe заново.
  • Экономия памяти. Если 10 программ используют одну и ту же DLL (например, системную user32.dll), в памяти будет только одна копия кода.
  • Совместное использование между разными языками. DLL можно вызывать из Python, C#, Java через FFI.

Пример с динамической библиотекой в CMake:

# CMakeLists.txt
add_library(my_math SHARED math.cpp math.h)  # собираем DLL/SO

add_executable(calculator main.cpp)
target_link_libraries(calculator PRIVATE my_math)  # линкуем динамически

# Чтобы DLL лежала рядом с exe (Windows):
set_target_properties(my_math PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

Важные технические нюансы

  1. DLL Hell. Вы правы, что DLL могут "ломаться". Если обновить библиотеку, но не обновить exe (или наоборот), могут быть краши. Решение: строгий контроль версий (SemVer) и использование манифестов в Windows.
  2. Экспорт символов. В Windows нужно явно помечать функции как __declspec(dllexport) в DLL и __declspec(dllimport) в клиенте. В Linux все символы экспортируются по умолчанию.
  3. Платформенная независимость. Для кроссплатформенности используйте макросы:
#ifdef _WIN32
  #define EXPORT __declspec(dllexport)
  #define IMPORT __declspec(dllimport)
#else
  #define EXPORT
  #define IMPORT
#endif

// В библиотеке
EXPORT int add(int a, int b);

// В клиенте
IMPORT int add(int a, int b);

Рекомендация для начинающих

Для вашего калькулятора используйте статическую библиотеку — проще и надёжнее. Для сторонних библиотек (Boost, OpenGL) почти всегда используйте динамическую линковку, так как они уже установлены в системе или идут в составе SDK. В Visual Studio это настраивается в свойствах проекта: Configuration Properties → C/C++ → Code Generation → Runtime Library (Multi-threaded DLL / Multi-threaded).

Запомните золотое правило: статическая линковка — для распространения, динамическая — для разработки и модульности.

<<>>

Дополнение: гибридный подход и современные альтернативы

Коллега выше всё верно описал. Хочу добавить, что в современных C++ проектах часто используют гибридный подход:

  • Свои внутренние модули (логика, модели данных) — статически, чтобы не плодить DLL.
  • Сторонние зависимости (Qt, Boost, OpenCV) — динамически, потому что они большие и часто обновляются.

Ещё один важный момент — размер исполняемого файла. Статическая линковка может увеличить exe на десятки мегабайт (особенно с Boost). Но на современных SSD это не проблема. Однако если вы пишете, например, игру или мобильное приложение — каждый килобайт на счету, и динамические библиотеки помогут сэкономить место.

Также обратите внимание на модули C++20. Они не заменяют библиотеки, но позволяют компилировать код быстрее и чище. В будущем, возможно, модули изменят подход к организации кода, но пока классические .lib/.dll остаются стандартом.

И последнее: в Linux динамические библиотеки имеют версионирование в имени файла (например, libfoo.so.1.2.3). Это решает проблему DLL Hell — система сама подбирает нужную версию. На Windows для этого используют Side-by-Side assemblies или NuGet-пакеты.