O básico que todo mundo sabe, mas poucas pessoas aplicam direito
Você está escrevendo uma função em C e precisa passar um array de strings para outra parte do código. A decisão entre usar ponteiros, vetores dinâmicos ou estruturas embutidas define se seu programa vai rodar em 3 milissegundos ou travar o servidor inteiro. Essa escolha nasce diretamente da compreensão do que são primitivos e derivados no contexto da linguagem que você está usando. Primitivos são os tipos elementares que a linguagem oferece nativamente: int, float, double, char, bool, void. Derivados são construções feitas a partir deles — arrays, ponteiros, structs, unions, classes, enums, referências. A linha entre os dois às vezes é tênue, especialmente em linguagens modernas como Rust ou Swift, onde até enums podem carregar dados.
O que é primitivos e derivados: a diferença prática que importa
No dia a dia, primitivos são armazenados por valor na pilha. Derivados podem ser armazenados por referência, na heap, ou de forma híbrida dependendo da linguagem. Em C, um int ocupa exatamente 4 bytes na maioria das arquiteturas modernas. Um int* também ocupa 4 ou 8 bytes (tamanho do ponteiro), mas aponta para um endereço na memória que pode conter um array inteiro, um struct com centenas de campos, ou nada se estiver mal inicializado. O erro mais comum que eu vejo em code review é alguém tratar derivado como se fosse primitivo. Passar um ponteiro por valor em vez de por referência, duplicar arrays inteiros em chamadas de função, ou não considerar o custo de alocação dinâmica quando um array fixo resolveria. Isso parece simples, mas gera problemas reais de performance.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Eu fiquei duas semanas caçando um bug em um sistema de trading onde um struct derivado estava sendo copiado implícita em cada chamada de API. O struct tinha um array de 256 floats dentro. Cada cópia ia para a heap e voltava. O sistema simplesmente não conseguia manter a latência necessária durante horários de pico. A solução foi transformar aquele campo em um ponteiro com gerenciamento manual de memória e usar reuse de buffers. O problema sumiu. Não eraComplexo, só precisava entender onde cada coisa vivia na memória. Em Java e C#, a história muda um pouco porque o garbage collector cuida da heap para você, mas o custo de alocação e coleta ainda existe. Em Python, praticamente tudo é objeto derivado — números inteiros são objetos, strings são imutáveis, listas são arrays dinâmicos. A abstração é útil até você precisar processar milhões de registros e descobrir que estar usando listas em vez de arrays numpy custou horas extras de computação.
Outro ponto que poucos mencionam: em linguagens como Go, os derivados têm regras de passagem muito específicas. Um slice é na verdade uma estrutura com ponteiro, len e cap. Passar um slice para uma função não copia os dados subjacentes, mas copia a estrutura do slice em si. Isso significa que modificações nos elementos afetam o original, mas grow o slice dentro da função não reflete fora dela. É um detalhe que causei bugs sérios em microserviços quando não prestei atenção. A regra prática é: quando possível, mantenha primitivos. Quando precisar de derivados, seja explícito sobre ownership e lifetime. Em C, use valgrind ou ASan para detectar vazamentos. Em Rust, o compilador já te obriga a pensar nisso. Em Python, prefira numpy arrays ou dataclasses com slots para reduzir overhead. A escolha do tipo certo economiza tempo de desenvolvimento e previne problemas que aparecem só em produção.