Introdução
C23 é a versão mais recente do padrão ISO C e trouxe melhorias importantes: constexpr, typeof, nullptr, _BitInt, atributos padronizados, #embed e uma semântica mais clara para funções sem parâmetros. A pergunta prática para quem escreve sistemas é outra: C23 resolve os problemas que fazem equipes considerarem Zig?
A resposta curta é: C23 moderniza a sintaxe e reduz algumas dores, mas não muda a natureza da linguagem. C continua sendo excelente quando ABI, portabilidade histórica e ecossistema legado são mais importantes que ergonomia. Zig tenta ocupar o mesmo espaço operacional de C com outra proposta: build integrado, cross-compilation simples, comptime, error unions, slices com tamanho, allocators explícitos e verificações úteis em debug.
Este artigo compara Zig com C23 especificamente — não com o C de 1989, mas com o C mais moderno disponível. Para comparação com C++, veja Zig vs C++. Para migração prática, consulte Guia de Migração: C para Zig e Como Migrar um Projeto C para Zig.
Resposta Rápida: Zig ou C23?
| Cenário | Escolha mais provável | Motivo |
|---|---|---|
| Código legado com ABI C pública e toolchain já aprovado | C23 | Menor risco organizacional e compatibilidade máxima |
| Projeto novo de CLI, runtime, ferramenta interna ou biblioteca de sistemas | Zig | Build/cross-compilation integrados e menos cola de infraestrutura |
| Firmware/regulatório com MISRA/CERT C obrigatório | C23 | O ecossistema de certificação ainda gira em torno de C |
| Migração gradual de uma base C existente | Zig + C | @cImport, zig cc e wrappers permitem trocar partes aos poucos |
| Ensino de programação de sistemas com feedback rápido | Zig | Erros explícitos, testes integrados e debug checks ajudam a aprender |
| Biblioteca que precisa ser consumida por qualquer linguagem via ABI | C23 ou Zig expondo ABI C | A ABI C continua sendo o contrato universal |
A regra prática é simples: C23 é a melhor evolução conservadora de C; Zig é a melhor aposta quando você quer manter controle de baixo nível, mas remover parte do atrito histórico de C.
O Que C23 Trouxe de Novo
C23 é uma evolução significativa:
constexpr: Variáveis e funções avaliadas em compilaçãotypeof: Inferência de tipo (finalmente!)nullptr: Constante nula tipada (substituiNULL)_BitInt(N): Inteiros de largura arbitrária- Atributos padrão:
[[nodiscard]],[[maybe_unused]],[[deprecated]] auto: Inferência de tipo para variáveis locais#embed: Incluir dados binários diretamente- Funções sem parâmetros:
void f()agora significa zero parâmetros
Essas melhorias são bem-vindas, mas Zig foi projetado desde o início para resolver problemas que C23 apenas mitiga.
Segurança de Memória
C23
C23 não resolve os problemas fundamentais de segurança de C:
// Todos estes bugs AINDA existem em C23:
int* p = malloc(sizeof(int));
free(p);
*p = 42; // use-after-free: undefined behavior
int arr[10];
arr[15] = 0; // buffer overflow: undefined behavior
char* s = NULL;
printf("%s", s); // null dereference: undefined behavior
C23 adicionou nullptr e constexpr, mas a linguagem continua fundamentalmente insegura em relação a memória.
Zig
Zig resolve esses problemas com verificações de segurança em tempo de compilação e runtime:
// Use-after-free: detectado em debug
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const p = try allocator.create(i32);
allocator.destroy(p);
// p.* = 42; // Erro: uso de memória liberada (em debug)
// Buffer overflow: sempre verificado
var arr: [10]i32 = undefined;
// arr[15] = 0; // panic: index out of bounds
// Null dereference: prevenido pelo sistema de tipos
const s: ?[]const u8 = null;
// s.len; // Erro de compilação: precisa unwrap optional
if (s) |valor| {
std.debug.print("{s}\n", .{valor});
}
Veja Segurança de Memória em Zig e Detectar Vazamentos.
Metaprogramação: constexpr vs Comptime
C23 constexpr
C23 constexpr é limitado — não permite loops, chamadas de função arbitrárias ou alocação em tempo de compilação:
// C23: constexpr básico
constexpr int MAX_TAMANHO = 1024;
constexpr int quadrado(int x) { return x * x; }
constexpr int tabela[5] = {0, 1, 4, 9, 16};
Zig comptime
Zig comptime executa código Zig arbitrário em tempo de compilação — incluindo loops, chamadas de função, e até criação de tipos:
// Zig: comptime completo
fn criarLookupTable(comptime n: usize) [n]u32 {
var tabela: [n]u32 = undefined;
for (&tabela, 0..) |*entry, i| {
entry.* = @intCast(i * i);
}
return tabela;
}
const TABELA = criarLookupTable(256); // calculada em compilação
fn HashMap(comptime K: type, comptime V: type) type {
return struct {
// tipo inteiro criado em compilação
entries: []Entry,
const Entry = struct { key: K, value: V };
};
}
A diferença é enorme: constexpr de C23 avalia expressões simples; comptime de Zig é uma linguagem de metaprogramação completa usando a mesma sintaxe.
Tratamento de Erros
C23
C23 não tem mecanismo de tratamento de erros integrado. O padrão continua sendo códigos de retorno e errno:
FILE* f = fopen("config.txt", "r");
if (f == NULL) {
perror("Erro ao abrir arquivo");
return -1;
}
// fácil esquecer de verificar
Zig
Zig tem error unions integrados ao sistema de tipos:
fn lerConfig(caminho: []const u8) !Config {
const arquivo = try std.fs.cwd().openFile(caminho, .{});
defer arquivo.close();
// ...
}
// O compilador OBRIGA tratamento do erro
const config = lerConfig("config.txt") catch |err| {
std.debug.print("Erro: {}\n", .{err});
return;
};
Veja Error Sets Customizados e Padrões Errdefer.
Sistema de Build
C23
C23 não define um sistema de build. Desenvolvedores C usam Make, CMake, Meson, Autotools, ou outros — cada um com sua própria sintaxe e complexidade:
CC=gcc
CFLAGS=-std=c23 -Wall -O2
programa: main.o utils.o
$(CC) $(CFLAGS) -o $@ $^
Zig
Zig tem um sistema de build integrado escrito em Zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "programa",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}
Veja Migrar de Makefile para build.zig e Migrar de CMake para build.zig.
Comparação Direta de Features
| Feature | C23 | Zig |
|---|---|---|
| Inferência de tipo | auto/typeof (limitado) | Completa |
| Null safety | nullptr (sintática) | Optionals ?T (semântica) |
| Bounds checking | Nenhum | Debug mode automático |
| Error handling | errno/códigos de retorno | Error unions integrados |
| Metaprogramação | constexpr (limitado) + macros | comptime (completo) |
| Generics | _Generic (muito limitado) | comptime generics |
| Sistema de build | Externo (Make/CMake) | Integrado (build.zig) |
| Testes | Externo (Unity, cmocka) | Integrados (test) |
| Cross-compilation | Via toolchain externo | Integrada |
| Package manager | Nenhum oficial | Integrado (zon) |
| Strings | char* (null-terminated) | Slices com tamanho |
| defer | Não existe | Sim |
| Undefined behavior | Extensivo (200+ formas) | Minimizado, detectado |
Preprocessador vs Comptime
O preprocessador C é uma das maiores fontes de problemas. C23 não o substitui:
C23 (preprocessador)
// Problemas clássicos do preprocessador
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int x = MAX(i++, j++); // bug: i++ ou j++ avaliado duas vezes
#define TAMANHO_BUFFER 1024
// Não é uma constante real, é substituição textual
Zig (comptime)
fn max(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
return if (a > b) a else b;
}
// Sem efeitos colaterais duplicados — é uma função real
const TAMANHO_BUFFER: usize = 1024;
// Constante real com tipo
Veja Substituir Macros C por Comptime Zig para um guia completo.
Interoperabilidade C-Zig
Zig pode usar código C23 diretamente:
const c = @cImport({
@cDefine("_GNU_SOURCE", {});
@cInclude("minha_lib_c23.h");
});
pub fn main() !void {
const resultado = c.minha_funcao_c23();
std.debug.print("Resultado: {}\n", .{resultado});
}
Veja Interoperabilidade C-Zig e Converter Ponteiros C para Zig.
Performance: O Ponto Onde a Comparação Fica Menos Ideológica
Em código nativo bem escrito, Zig e C23 podem chegar a performances muito próximas. Ambos compilam para binários nativos, permitem controle de layout de dados, evitam garbage collector obrigatório e conseguem usar instruções específicas de arquitetura quando necessário. A diferença não é “Zig é sempre mais rápido” ou “C é sempre mais rápido”. A diferença real está no caminho até um binário rápido, reproduzível e fácil de medir.
Em C, performance costuma depender de uma combinação externa: compilador, flags, sistema de build, bibliotecas, sanitizers, profiler e scripts de release. Em Zig, mais dessa configuração fica dentro do próprio projeto:
zig build -Doptimize=ReleaseFastdocumenta o modo de otimização no fluxo oficial do projeto.ReleaseSafepermite uma opção intermediária quando segurança em runtime ainda importa.zig ccpode compilar dependências C com uma toolchain mais previsível.- Cross-compilation é uma função do compilador, não uma coleção de pacotes externos por plataforma.
- Benchmarks e testes podem viver no mesmo fluxo de
zig build test.
Para aprender a medir sem cair em microbenchmark enganoso, veja Benchmarking em Zig: medir performance e, para comparação com outras linguagens de sistemas, Zig vs Rust e Zig vs Go.
Migração Gradual: O Caso Mais Realista
Poucas equipes jogam fora uma base C grande para reescrever tudo em uma linguagem nova. O caminho mais seguro costuma ser incremental:
- Usar
zig cccomo compilador C em um projeto existente para simplificar cross-compilation e releases. - Criar testes de borda para partes críticas antes de trocar implementação.
- Escrever novos módulos em Zig que exponham uma ABI C simples.
- Importar headers C com
@cImportenquanto a fronteira entre as linguagens ainda muda. - Mover lógica de build para
build.zigquando o projeto já tiver uma fatia Zig relevante. - Substituir macros perigosas por funções ou
comptimeonde houver bugs recorrentes.
Esse caminho reduz risco porque C e Zig podem conviver no mesmo binário. Você não precisa vender uma reescrita total para ganhar benefícios práticos: pode começar por uma ferramenta CLI, uma biblioteca de parsing, uma rotina de serialização, um worker de alta performance ou uma parte difícil de cross-compilar.
Leia também Interop C em Zig, Migrar projeto C para Zig e Cross-compilation em Zig.
Quando C23 Ainda Faz Sentido
- Projetos com requisitos de conformidade MISRA/CERT C
- Ecossistemas que mandatam C (Linux kernel, firmware regulatório)
- Equipes com décadas de experiência em C e resistência a mudança
- Bibliotecas que precisam de compatibilidade ABI C máxima
- Bases legadas onde a maior parte do risco está em processo, certificação e tooling, não em sintaxe
Quando Zig É a Melhor Aposta
Zig faz mais sentido quando a equipe quer continuar perto do metal, mas está cansada de manter uma pilha de ferramentas ao redor de C. Alguns sinais fortes:
- Você precisa gerar binários para Linux, macOS, Windows, ARM e x86 sem manter múltiplas toolchains.
- O projeto tem muitos
Makefile, scripts de release e flags duplicadas. - O time quer remover macros perigosas sem adotar C++ ou Rust.
- Você quer testes e build reproduzíveis como parte da experiência padrão.
- Você está escrevendo uma CLI, um runtime, uma lib nativa, um parser, um servidor leve ou uma ferramenta de infraestrutura.
- Você precisa interoperar com C, mas quer escrever as novas partes com um sistema de tipos mais explícito.
FAQ: Zig vs C Moderno
Zig substitui C23?
Não universalmente. Zig pode substituir C em muitos projetos novos e em partes isoladas de bases existentes, mas C23 continua mais conservador para firmware regulado, bibliotecas com contrato ABI histórico e ambientes onde o compilador C é obrigatório.
C23 ficou seguro como Zig?
Não. C23 melhora a linguagem, mas não adiciona null safety semântica, bounds checking padrão, error unions, slices com tamanho ou um modelo de allocators como parte central da experiência. Bugs clássicos de C continuam possíveis.
Zig é mais rápido que C?
Não há resposta universal. Zig e C podem gerar código nativo extremamente rápido. A vantagem de Zig costuma aparecer mais em ergonomia de build, cross-compilation, testes e controle explícito do projeto do que em uma promessa absoluta de benchmark.
Posso usar C e Zig no mesmo projeto?
Sim. Esse é um dos pontos fortes de Zig. Você pode importar headers C com @cImport, compilar C com zig cc, chamar bibliotecas C existentes e expor uma ABI C para consumidores externos.
Devo aprender C antes de Zig?
Ajuda, mas não é obrigatório. C ensina a história e a ABI que ainda governam sistemas. Zig pode ser uma porta de entrada mais segura para aprender memória, ponteiros, layout e build moderno sem aceitar todas as armadilhas de C como padrão.
Conclusão
C23 é uma melhoria real sobre C17, mas não resolve os problemas fundamentais de C: segurança de memória, undefined behavior, ausência de sistema de build, e preprocessador frágil. Zig foi projetado para resolver exatamente esses problemas mantendo o mesmo nível de controle que C oferece.
Se você está começando um projeto novo de sistemas, Zig oferece muitas das vantagens de C com segurança e ergonomia modernas. Se está mantendo código C existente, considere a migração gradual descrita em Como Migrar um Projeto C para Zig.
Se você está avaliando alternativas modernas ao C, confira também nosso conteúdo sobre Rust, outra linguagem que busca substituir C com foco em segurança de memória.
Para começar, visite Introdução ao Zig e Zig para Programadores C.