Представьте: вы разработали приложение на C++, протестировали на своей машине — всё работает идеально. Отправляете бинарник коллеге, и он присылает скриншот: «MSVCR140.dll not found». Вы вздыхаете и начинаете объяснять про Visual Studio Redistributable. Это не разработка, это археология зависимостей. Один инженер решил, что с Windows native development пора покончить с этим цирком, и переписал всю цепочку сборки с нуля.
На Linux вы компилируете программу через GCC, получаете один бинарник и запускаете его где угодно. На macOS — то же самое с Clang. На Windows? Добро пожаловать в зависимостный лабиринт.
Проблема начинается с того, что Microsoft C Runtime (MSVCRT) распространяется как набор DLL-файлов. Когда вы компилируете программу через MSVC, компилятор генерирует объектные файлы, которые линкуются с этими библиотеками. Результат — ваш EXE требует MSVCR140.dll, VCRUNTIME140.dll, ucrtbase.dll и ещё десяток системных компонентов.
Формально Microsoft предлагает решение: Visual Studio Redistributable — установочный пакет, который копирует все нужные DLL в систему. Но это порождает новые проблемы:
Вторая проблема — сам MSVC. Это не просто компилятор, это экосистема размером десятки гигабайт: Visual Studio IDE, Windows SDK, platform toolsets, специфичные расширения языка. Если вы хотите собирать проекты через CI/CD, вам нужно разворачивать виртуальную машину с полной установкой Visual Studio. Это медленно, дорого и сложно воспроизвести.
Третья проблема — backwards compatibility. Universal C Runtime (UCRT) появился только в Windows 10. Если вы хотите поддерживать Windows 7 или 8, нужно явно таргетироваться на старые версии runtime, патчить манифесты, возиться с side-by-side assemblies. Один бинарник для всех версий Windows? Забудьте.
Решение пришло из неожиданного места — из экосистемы языка Zig. Zig — это современный системный язык, который позиционируется как «better C». Но главная его фича — это компилятор, который умеет кросс-компилировать под любую платформу без дополнительных зависимостей.
Марлер — автор проекта Zigstrap — использовал эту возможность, чтобы создать полноценный C/C++ toolchain для Windows, который работает по принципу «один бинарник, zero dependencies».
Архитектура выглядит так:
Ключевое отличие от MSVC: весь код стандартной библиотеки (malloc, printf, fopen и т.д.) не подтягивается как DLL, а вшивается в итоговый EXE-файл. Никаких внешних зависимостей — только обращения к ntdll.dll, которая есть даже на Windows XP.
Если вам интересны технические детали bootstrapping-процесса и примеры реальных проектов, собранных через Zigstrap (Git, Nginx, SQLite), смотрите видеоразбор — там мы показываем полный цикл сборки с нуля.
Работа с Zig CC выглядит обманчиво просто. Вместо:
cl.exe /EHsc /O2 myapp.cpp /link /OUT:myapp.exe
Вы пишете:
zig cc myapp.c -o myapp.exe
Под капотом происходит следующее:
Этап 1: парсинг и lowering
Zig-компилятор парсит C-код, строит AST и переводит его в промежуточное представление Zig IR. Это не LLVM IR — Zig использует собственный бэкенд для кодогенерации.
Этап 2: разрешение стандартной библиотеки
Когда код вызывает printf, компилятор не линкуется с MSVCRT. Вместо этого он подтягивает реализацию из zig/lib/libc/mingw — это минималистичная реализация стандартной библиотеки, которая использует напрямую Win32 API через ntdll.
Этап 3: кодогенерация
Zig генерирует машинный код для x86-64 или ARM, включая все зависимости. Итоговый бинарник содержит весь код — и ваш, и стандартной библиотеки.
Этап 4: линковка
Линкер Zig создаёт PE-файл (Portable Executable — формат Windows EXE), прописывает импорты только для ntdll.dll, и генерирует финальный бинарник.
Результат: EXE-файл размером 150-300 КБ (в зависимости от сложности), который работает на любой Windows без дополнительных установок.
Классическая проблема компиляторов — для их сборки нужен другой компилятор. Обычно на Windows это означает обязательную установку MSVC или MinGW. Zigstrap решает это через multi-stage bootstrap:
Stage 0: Скачивается minimal Zig binary (около 50 МБ), собранный на CI-серверах проекта. Это самодостаточный бинарник, который умеет компилировать Zig-код.
Stage 1: Этот бинарник компилирует полноценный Zig-компилятор из исходников, но без оптимизаций (debug mode). Сборка занимает 3-5 минут.
Stage 2: Полноценный компилятор пересобирает сам себя с оптимизациями (release mode). Это финальная версия, которая используется для реальной работы.
Весь процесс автоматизирован одним PowerShell-скриптом. Никаких прав администратора, никаких зависимостей — только интернет-соединение для скачивания stage 0.
Автор статьи протестировал сборку нескольких production-проектов:
Git for Windows: собрался без патчей, статический бинарник работает на Windows 7/10/11. Размер увеличился на 20% из-за статической линковки, но зато zero dependencies.
SQLite: чистая C-кодовая база, собралась тривиально. Benchmark показал идентичную производительность с MSVC-версией.
Nginx: потребовались минорные патчи для совместимости с Zig libc, но после этого собрался статически. Один EXE-файл вместо связки бинарников и DLL.
ImageMagick: сложный проект с десятками зависимостей. Все библиотеки (libpng, libjpeg, zlib) собрались статически через Zig CC. Итоговый бинарник — 15 МБ, но полностью переносимый.
Несмотря на впечатляющие результаты, есть нюансы:
C++ поддержка: Zig CC корректно компилирует C++11/14, но с C++17/20 могут быть проблемы. Concepts, modules, ranges — всё это ещё не полностью поддерживается.
Отладка: Visual Studio Debugger не понимает Zig debug symbols out of the box. Нужно использовать GDB или LLDB, что требует привыкания.
Экосистема: Некоторые библиотеки используют MSVC-специфичные расширения (__declspec, #pragma intrinsic). Для их сборки нужны патчи.
Размер бинарника: Статическая линковка увеличивает размер на 10-30%. Для embedded-систем это может быть критично.
Но даже с этими ограничениями преимущества перевешивают: переносимость, простота развёртывания, отсутствие версионных конфликтов.
Вопрос резонный: если статическая линковка решает столько проблем, почему Microsoft продолжает навязывать DLL-модель?
Причина — бизнес-модель. DLL-зависимости создают vendor lock-in: разработчики вынуждены использовать Visual Studio, обновлять SDK, платить за подписки. Это контроль над экосистемой.
Вторая причина — безопасность (в теории). Если в MSVCRT находят уязвимость, Microsoft может пропатчить DLL через Windows Update, и все приложения автоматически получат фикс. Со статической линковкой нужно пересобирать каждый бинарник.
Но на практике это не работает: у большинства приложений есть специфичные версии runtime, которые не обновляются автоматически. Плюс security-критичные приложения всё равно пересобираются при каждом патче.
Zigstrap и Zig CC — это не просто инструменты, это proof of concept: Windows native development может быть таким же простым, как на Linux. Один компилятор, один бинарник, zero configuration.
Сообщество уже использует эту технологию для embedded-проектов, кросс-компиляции под Windows ARM, CI/CD-пайплайнов без Docker. GitHub Actions с Zig CC собирают релизы для всех платформ за один проход — никаких VM, никаких контейнеров.
Ключевой вывод: проблемы Windows native development — это не техническое ограничение, а архитектурное решение Microsoft. И его можно обойти.
Если вы разработчик на C/C++ и устали от DLL-археологии, MSVC-хаоса и redistributable-квестов — попробуйте Zigstrap. Это займёт 10 минут установки и даст toolchain, который просто работает.
Статическая сборка — это не серебряная пуля, но для большинства приложений это оптимальный выбор. Переносимость, предсказуемость, отсутствие версионных конфликтов. Как native development должен был работать с самого начала.