Pular para o conteúdo principal

Gerenciamento de Memória

Como uma linguagem gerencia memória é uma das decisões de design mais fundamentais. Veja como diferentes linguagens lidam com alocação, desalocação e segurança de memória.

Visão Geral do Modelo de Memória​

// GARBAGE COLLECTED (automatic)
// V8 engine uses generational GC

// Objects are heap-allocated, GC handles cleanup
let obj = { name: "John" };
obj = null; // Eligible for garbage collection

// WeakRef (allows GC to collect)
let target = { data: "important" };
let weakRef = new WeakRef(target);
weakRef.deref(); // target or undefined

// WeakMap / WeakSet (weak references)
const cache = new WeakMap();
cache.set(obj, "cached");
// When obj is GC'd, entry is removed

// FinalizationRegistry (cleanup callback)
const registry = new FinalizationRegistry((value) => {
console.log("Object collected:", value);
});
registry.register(obj, "my object");

// Memory leaks to watch for:
// - Forgotten timers/intervals
// - Closures holding references
// - Detached DOM nodes
// - Global variables

Principais Conclusões​

  • GC vs ownership vs manual -- A maioria das linguagens (JavaScript, Python, Go, Java, C#, Ruby, PHP) usa coleta de lixo: você aloca, e o runtime recupera quando os objetos ficam inalcançáveis. Rust usa ownership em tempo de compilação: cada valor tem um dono, e o compilador impõe regras de borrowing. C, C++ e Zig usam memória manual: você chama malloc/free, new/delete ou allocator.alloc()/allocator.free(). GC simplifica o desenvolvimento mas adiciona latência e não-determinismo; ownership (Rust) oferece segurança sem GC; gerenciamento manual oferece controle total mas exige disciplina. Escolha GC para produtividade; Rust para segurança e performance; C/C++/Zig para sistemas, embarcados ou quando precisar de alocação previsível.

  • Contagem de referências -- Swift usa ARC (tempo de compilação): o compilador insere chamadas de retain/release, e weak/unowned quebram ciclos. C++ tem std::shared_ptr para ownership compartilhado: auto sptr = std::make_shared<int>(42); Rust oferece Rc<T> (single-threaded) e Arc<T> (thread-safe) para ownership compartilhado. PHP e Python usam contagem de referências como parte do GC (mais detecção de ciclos). Contagem de referências fornece limpeza determinística quando a última referência é liberada, mas pode ter overhead e ciclos. Use ARC/shared_ptr/Rc quando precisar de ownership compartilhado; prefira o ownership do Rust quando ownership único for suficiente.

  • Garantias de segurança de memória -- Rust fornece segurança de memória sem GC via ownership e borrowing: o compilador rejeita use-after-free, double-free e data races. Por exemplo, let s2 = s1; move s1, então s1 não é mais utilizável. C e C++ não têm tais garantias; bugs como free(ptr); use(ptr) ou buffer overflows são comuns. O modelo manual do Zig é mais seguro que C (sem comportamento indefinido em código seguro, allocators explícitos) mas não previne use-after-free. Escolha Rust quando segurança de memória é crítica; use C/C++ apenas quando precisar de controle de baixo nível e aceitar o risco; Zig fica entre eles.

  • Padrões de limpeza de recursos -- RAII (C++), defer (Go, Zig), using (C#) e with (Python) vinculam o tempo de vida do recurso ao escopo. C++: std::ifstream file("data.txt"); -- fechado quando o escopo termina. Zig: defer file.close();. C não tem padrão embutido; você deve parear manualmente alocação/liberação e tratar erros. Esses padrões previnem vazamentos e simplificam caminhos de erro. Prefira linguagens com limpeza estruturada ao gerenciar arquivos, conexões ou recursos customizados.

  • Alocação heap vs stack -- Tipos de valor (primitivos, structs em C#/Swift/Rust) geralmente vivem na stack; tipos de referência e alocações dinâmicas vão para o heap. Go usa análise de escape para decidir: return &x força alocação no heap. Zig requer um allocator explícito para alocação no heap: allocator.create(i32) e allocator.destroy(ptr). O stackalloc e Span<T> do C# permitem alocação na stack para performance. Entender alocação ajuda com performance e previsibilidade. Para caminhos quentes, prefira alocação na stack quando possível; use Zig ou C quando precisar de controle explícito sobre a estratégia de alocação.