Introdução
C23 é a mais recente versão do padrão ISO C, trazendo melhorias como constexpr, typeof, nullptr, _BitInt, atributos padronizados e mais. A pergunta que muitos desenvolvedores C fazem é: “C23 resolve os problemas que me fariam considerar Zig?”
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.
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.
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
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 todas as 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.
Para começar, visite Introdução ao Zig e Zig para Programadores C.