Zig como Compilador C/C++: zig cc e zig c++ sem Setup

Quem já tentou compilar um projeto C/C++ decente em mais de um sistema operacional conhece a dor: instalar GCC no Linux, MinGW no Windows, toolchain da Apple no macOS, configurar sysroot, brigar com versão de glibc e ainda assim depender de um CI que nem sempre reproduz a máquina local. O que poucos desenvolvedores percebem é que o próprio Zig já é um compilador C e C++ completo e portátil.

Os comandos zig cc e zig c++ são invólucros drop-in sobre o Clang/LLVM que vêm embutidos no próprio binário do Zig, acompanhados das libc de cross-compilação (musl, mingw-w64, glibc e variantes). Isso transforma o Zig em uma toolchain C/C++ que você baixa uma vez e usa para compilar projetos existentes em praticamente qualquer alvo, sem instalar SDK por plataforma.

Este guia mostra como usar o Zig como compilador C/C++ no dia a dia: compilar código C/C++ legado, substituir GCC/Clang/MSVC em pipelines, cross-compilar dependências sem sysroot e integrar tudo aos seus projetos Zig. Ele complementa o guia de cross-compilation com Zig, o material de interoperabilidade com C e o build system do Zig. A ideia central é a mesma: menos setup, mais previsibilidade.

O que é zig cc e por que isso importa

O binário oficial do Zig inclui o Clang, o LLD (linker) e os fontes das libc que ele sabe compilar. Quando você executa zig cc, o Zig age como um compilador C que aceita a maior parte das flags que você já conhece (-O, -I, -L, -l, -D, -std=c11), mas com uma vantagem estrutural: ele resolve libc, headers e linker automaticamente para o alvo escolhido.

A consequência prática é enorme para quem trabalha com C/C++ e sistemas:

  • um único download substitui GCC, Clang, MinGW e partes do fluxo de MSVC;
  • cross-compilar para Windows a partir do Linux (ou o contrário) não exige instalar toolchain adicional;
  • o CI fica reproduzível porque a toolchain viaja junto com o projeto;
  • dependências em C/C++ de um projeto Zig podem ser construídas com a mesma ferramenta que já cuida do resto.

Antes de qualquer benchmark ou recurso avançado, esse é o ganho real: previsibilidade. O mesmo zig cc 0.14.1 produz o mesmo binário na sua máquina, na do colega e no runner do CI.

zig cc versus zig c++ versus zig build

São três coisas diferentes e vale separar antes de continuar:

ComandoPara que serveQuando usar
zig ccCompila código C (drop-in para gcc/clang)Projetos C, dependências em C, scripts de build
zig c++Compila código C++ (drop-in para g++/clang++)Projetos C++, bibliotecas que precisam de C++
zig buildOrquestra um projeto Zig via build.zigQuando o projeto principal é em Zig

A confusão comum é achar que zig build é a única forma de usar o Zig. Ele é o caminho para projetos nativamente em Zig, mas zig cc/zig c++ existem justamente para a base enorme de código C e C++ que já existe no mundo. Se você está construindo um projeto Zig, use zig build; se está compilando algo em C/C++ que já existe, use zig cc ou zig c++.

Compilando um projeto C existente

Comece pelo caso mais simples: um programa em C que precisa virar binário.

# main.c
# #include <stdio.h>
# int main(void) { printf("olá do zig cc\n"); return 0; }

zig cc main.c -o app
./app

Sem Makefile, sem configure, sem instalar nada além do próprio Zig. Para algo um pouco maior, com vários arquivos e includes:

zig cc -O2 -std=c11 -Iinclude src/main.c src/parser.c src/io.c -lm -o app

As flags são as mesmas que você usaria com GCC ou Clang. É exatamente por isso que a substituição costuma ser tão direta em projetos legados: a interface é compatível.

Substituindo GCC, Clang e MSVC com CC=zig cc

A maneira mais limpa de adotar o Zig como compilador em um projeto C/C++ existente não é reescrever o build, e sim apontar as variáveis de ambiente para o Zig. Makefiles, CMake, Autotools e Meson em geral respeitam CC, CXX, LD e afins.

export CC="zig cc"
export CXX="zig c++"

make            # agora compila com o Zig como compilador
cmake -B build && cmake --build build

Em CMake, às vezes é preciso também passar o linker:

cmake -B build \
  -DCMAKE_C_COMPILER="zig" -DCMAKE_C_COMPILER_ARG1="cc" \
  -DCMAKE_CXX_COMPILER="zig" -DCMAKE_CXX_COMPILER_ARG1="c++"

O detalhe do ARG1 aparece porque alguns sistemas de build esperam um executável único (zig) e o cc/c++ vira argumento. Para quem está saindo do CMake, vale revisar o roteiro de migrar CMake para build.zig; a diferença é que aqui a ideia é manter o CMake e só trocar a toolchain, não o sistema de build.

Cross-compilação C/C++ sem configurar sysroot

Aqui mora o recurso que mais economiza tempo. O Zig embute as libc necessárias para cross-compilar, então um único comando compila para um alvo diferente sem que você precise baixar toolchain, sysroot ou headers extras.

# Linux → Windows (sem instalar MinGW)
zig cc main.c -target x86_64-windows-gnu -o app.exe

# Linux/macOS → Linux com musl (binário estável, portável)
zig cc main.c -target x86_64-linux-musl -o app

# Qualquer SO → macOS (ABI arm64)
zig cc main.c -target aarch64-macos-none -o app

# Linux → ARM Linux (comum em embarcados/Raspberry Pi)
zig cc main.c -target aarch64-linux-gnu -o app

O formato do -target é <cpu>-<os>-<libc>. Para alvos sem libc (bare metal, firmware), use -none. Isso conversa diretamente com o material de sistemas embarcados e IoT e com o guia de cross-compilation para FreeBSD e NetBSD: a mesma toolchain que gera seu binário de servidor gera o firmware e o binário de desktop.

Um cuidado importante: cross-compilar funciona bem quando o projeto depende apenas de libc padrão e bibliotecas estáticas que você também compila. Se o projeto liga dinamicamente contra bibliotecas de sistema do alvo (uma libfoo.so específica), você ainda precisa dessas bibliotecas. O zig cc resolve headers e libc embutidos, não magicamente baixa dependências de terceiros.

Compilando dependências C/C++ para um projeto Zig

Na prática, muitos projetos Zig dependem de bibliotecas em C. O fluxo natural é compilar essas dependências com o mesmo zig cc que já cuida do resto, garantindo que alvo, ABI e libc batam. O artigo sobre dependências e build.zig.zon trata de pacotes Zig; aqui o foco é dependência em C.

No build.zig, adicione fontes C diretamente ao seu passo de build:

const exe = b.addExecutable(.{
    .name = "meuapp",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

// Compila um arquivo C com a mesma toolchain/alvo do projeto.
exe.root_module.addCSourceFile(.{
    .file = b.path("vendor/mini.c"),
    .flags = &.{ "-std=c11", "-O2" },
});

// Caso precise de headers do projeto C.
exe.root_module.addIncludePath(b.path("vendor/include"));

Se a dependência é maior e usa o próprio configure/Makefile, a abordagem é rodar esse build apontando CC=zig cc para o target desejado e depois ligar o .a/.lib gerado ao seu executável Zig com exe.linkLibrary(...) ou exe.addObject. O ganho é que uma única toolchain compila a dependência e o código Zig no mesmo alvo, sem desalinhamento de ABI.

O que já vem embutido (e o que não vem)

É honesto separar expectativas. Junto com zig cc, você já tem:

  • Clang/LLVM para C, C++ e Objective-C;
  • LLD como linker integrado;
  • libc de cross-compilação: musl, mingw-w64, glibc (várias versões) e suporte limitado a MSVC;
  • headers de libc para os alvos suportados.

O que o zig cc não resolve sozinho:

  • bibliotecas de terceiros (libssl, libcurl, libpq etc.) precisam ser compiladas ou obtidas para o alvo;
  • SDKs proprietários ainda exigem instalação em alguns casos (alguns alvos macOS e o windows-msvc têm restrições);
  • sanitizers e ferramentas de debugging avançadas nem sempre têm paridade total com Clang standalone.

Para integração com banco de dados, por exemplo, você continua precisando da libpq do alvo — veja o guia de PostgreSQL com libpq em produção. O zig cc encurta o caminho da toolchain, mas não substitui planejar suas dependências.

CI/CD com zero setup de toolchain

Em pipelines de CI, o ganho é imediato. Em vez de instalar gcc-multilib, mingw-w64, versão específica de clang e cache de sysroot, você baixa um tarball do Zig e tem tudo. Para releases multiplataforma, combine com o roteiro de GitHub Actions para Zig:

# matriz de alvos, mesma toolchain
for target in \
  x86_64-linux-musl \
  aarch64-linux-gnu \
  x86_64-windows-gnu \
  x86_64-macos-none; do
  zig cc src/main.c -target "$target" -o "app-$target"
done

Cada binário sai da mesma execução, do mesmo runner, com a mesma versão de Zig. Isso reduz drásticamente a categoria inteira de bugs “funciona na minha máquina, quebra no CI”.

Quando não usar zig cc

Nem toda situação pede o Zig como compilador C/C++. Evite trocar quando:

  • o projeto já tem toolchain validada e auditada e não há demanda de cross-compilação;
  • você precisa de um recurso específico de MSVC (como algumas opções de linker do Windows) que ainda não tem paridade;
  • o time depende de sanitisadores/profilers que exigem o Clang standalone ou GCC com versão específica;
  • a integração depende fortemente de um SDK proprietário que o Zig não cobre.

A regra prática: use zig cc quando o objetivo for portabilidade e reprodutibilidade, especialmente com cross-compilação. Se o objetivo for explorar recursos experimentais de um compilador específico, mantenha esse compilador.

Checklist antes de padronizar a toolchain

Antes de adotar zig cc como compilador C/C++ padrão do projeto, confira:

  • a versão do Zig está fixada no projeto e no CI (via build.zig.zon ou cache de download);
  • os alvos necessários (-target) foram testados de verdade, não assumidos;
  • as dependências em C/C++ são compiladas com o mesmo CC=zig cc do alvo;
  • os binários de release rodam nos sistemas alvo (principalmente Windows e macOS);
  • o CMake/Makefile está passando o linker corretamente quando necessário;
  • há um fallback documentado caso um alvo deixe de ser suportado;
  • o tamanho do binário e o uso de sanitizers foram checados para builds de debug;
  • o time sabe que zig cc e zig build são coisas diferentes.

Perguntas frequentes

zig cc é o Clang? Sim, por baixo dos panos é o Clang/LLVM, com LLD e libc gerenciados pelo próprio Zig. A vantagem é o empacotamento e a resolução automática de libc por alvo.

Preciso instalar o MinGW para gerar .exe? Não. Com -target x86_64-windows-gnu, o Zig usa o mingw-w64 embutido. Para -windows-msvc, há suporte, mas com mais ressalvas.

zig cc compila C++? Para C++ use zig c++. Ele linka a libc++ corretamente. Para projetos mistos, geralmente basta definir CXX=zig c++.

Isso substitui o GCC em distros Linux? Para compilar seus projetos, sim. Para gerenciar pacotes do sistema, não — a toolchain de pacotes do seu SO segue sendo a fonte de verdade do sistema.

Funciona para firmware/embarcados? Sim, é um dos pontos fortes. Para alvos bare metal e RTOS, combine com o conteúdo sobre firmware no ESP32 com FreeRTOS.

Próximos passos

Se você vai padronizar o Zig como toolchain C/C++, leia também o guia de cross-compilation, a interoperabilidade com C e como migrar um projeto C para Zig. Para comparar com outra linguagem de sistemas que também precisa de uma toolchain C/C++ para dependências nativas, veja como o tema aparece em Rust para embarcados: a diferença é que o Rust delega o C para o compilador do sistema (via crate cc), enquanto o Zig já embute o compilador e as libc de cross-compilação — o que costuma eliminar boa parte da configuração por plataforma.

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.