Você pode apontar para quase tudo em C. A linguagem permite criar ponteiros para tipos primitivos, arrays, funções e tipos definidos pelo usuário. Mas é nos indicadores de estruturas que as coisas ficam interessantes. Eles são extremamente comuns. Se você estiver escrevendo código C, provavelmente os encontrará com frequência.
Aqui está um exemplo básico. Mostra como você define uma estrutura e depois cria um ponteiro para ela.
Este trecho faz mais do que apenas atribuir um endereço de memória. Estabelece uma relação entre a variável ptr e a estrutura p1. O ponteiro contém o endereço da estrutura. Ele permite que você acesse os membros x e y indiretamente. Este é um padrão fundamental. Você vê isso em listas vinculadas, alocação dinâmica de memória e argumentos de função.
Por que usar ponteiros para estruturas?
Copiar estruturas inteiras é caro. Se sua estrutura contém muitos dados, passá-los por valor para uma função significa que o compilador fará uma cópia completa. Isso desperdiça tempo e memória. Usar um ponteiro evita a cópia. Em vez disso, você passa o endereço. A função pode modificar os dados originais sem sobrecarga.
Há também a questão do dimensionamento dinâmico. Arrays têm tamanhos fixos em tempo de compilação. As estruturas não resolvem necessariamente o problema dinâmico por si mesmas. Mas quando você combina uma estrutura com malloc, você obtém armazenamento de dados flexível. Você pode alocar uma estrutura no heap. Um ponteiro para essa estrutura permite gerenciar seu tempo de vida manualmente.
Acessando membros por meio de ponteiros
Depois de ter um ponteiro, você precisa acessar os dados contidos nele. Você não pode usar o operador ponto. A expressão ptr.x falhará na compilação. ptr é um endereço, não a estrutura em si. Você deve desreferencia-lo primeiro.
Existem duas maneiras de fazer isso. O primeiro é detalhado.
Os parênteses são obrigatórios aqui. O operador ponto tem precedência maior que o operador de desreferência. Sem eles, o compilador tenta acessar ptr.x, que não existe.
A segunda maneira é mais limpa. C fornece o operador de seta ->. Ele combina desreferenciação e acesso de membros em uma única etapa.
Esta sintaxe é padrão. É o que você verá em bases de código reais. Lê-se naturalmente como “vá para o objeto apontado por ptr e obtenha seu membro x.” É conciso. Reduz a desordem visual.
Armadilhas Comuns
Ponteiros nulos são um risco. Se você declarar um ponteiro, mas não o inicializar, ele apontará para lixo. Acessar ptr->x em um ponteiro nulo causa uma falha de segmentação. Você deve verificar se há nulo antes de usar o ponteiro.
Esta verificação não é opcional em código robusto. Evita travamentos.
Outro problema são os vazamentos de memória. Se você alocar memória com malloc e perder o ponteiro, essa memória permanecerá reservada até o programa terminar. Você precisa acompanhar cada alocação. free() é de sua responsabilidade. Esqueça
Ponteiros para estruturas em C geralmente atrapalham os desenvolvedores que se sentem confortáveis com ponteiros básicos, mas ficam confusos na sintaxe. Considere uma definição simples de registro para um perfil de usuário.
estrutura typedef {
nome do caracter[21];
cidade char[21];
estado do caractere[3];
} Rec;
typedef Rec *RecPointer;
Aqui, RecPointer é apenas um apelido para um ponteiro para uma estrutura Rec. Ao declarar uma variável desse tipo, você está lidando com um endereço de memória, não com os dados em si.
RecPointer r;
A variável r ocupa quatro bytes em um sistema de 32 bits (ou oito em um sistema de 64 bits). É apenas um ponteiro. Não contém o nome, cidade ou estado. Para armazenar dados reais, você deve alocar memória no heap.
r = (RecPointer)malloc(sizeof(Rec));
Esta chamada malloc reserva 45 bytes. São 21 bytes para o nome, 21 para a cidade e 3 para o estado, mais um byte para preenchimento ou alinhamento para atender aos limites da memória. Agora r aponta para um bloco válido de memória que se comporta exatamente como uma estrutura Rec.
Acessando membros via desreferência
Para interagir com os dados, você deve desreferenciar o ponteiro. É aqui que surgem os erros de precedência. Você pode acessar membros usando a notação de ponto padrão, mas deve colocar a desreferência entre parênteses.
strcpy((r).nome, “Leigh”);
strcpy(( r).cidade, “Raleigh”);
strcpy((*r).estado, “NC”);
Observe a sintaxe. (*r).name está correto. Se você escrever *r.name, a compilação falhará. Por que? Porque o operador ponto tem precedência maior que o operador de desreferência. O compilador interpreta *r.name como *(r.name). Como r é um ponteiro, r.name é uma sintaxe inválida e a expressão é recolhida. Os parênteses forçam a desreferência *r a acontecer primeiro, produzindo a estrutura, e então o operador ponto acessa o campo name.
É tedioso digitar. Parece confuso. Isso convida a erros.
A notação de seta
C fornece uma maneira mais limpa de lidar com isso. O operador de seta -> é um açúcar sintático para (*pointer).member. Não é um mecanismo diferente. Não é um novo operador que altera a forma como a memória é acessada. É simplesmente uma maneira mais curta de escrever a desreferência e o acesso dos membros em uma única etapa.
strcpy(r->nome, “Leigh”);
Isso é idêntico a strcpy((*r).name, "Leigh"). Ele salva dois caracteres. Isso elimina a necessidade de parênteses aninhados. É a maneira padrão pela qual a maioria dos desenvolvedores C interage com ponteiros de estrutura.
Implicações no gerenciamento de memória
A chamada free(r) é obrigatória. A memória foi alocada do heap. Se você não liberar, ele vazará. O próprio ponteiro r, a variável de quatro bytes que contém o endereço, é local para o quadro de pilha ou escopo global, mas os dados para os quais ele aponta residem em outro lugar.
Ao usar r->name, você está modificando os dados no endereço armazenado em r. O ponteiro r permanece inalterado. O endereço faz

Alocar memória para arrays dinamicamente é um elemento básico da programação C, mas requer a compreensão de como os ponteiros interagem com os blocos de memória bruta. Quando você precisa de um array de tamanho fixo que não é conhecido em tempo de compilação, a alocação de pilha padrão não será suficiente. Você tem que alcançar a pilha.
O trecho de código abaixo demonstra um padrão comum:
Aqui, malloc reserva espaço para dez inteiros. A conversão para (int *) garante que o ponteiro corresponda ao tipo esperado, embora os compiladores C modernos frequentemente alertem contra conversões explícitas para void*. O loop então inicializa cada elemento com zero usando notação de subscrito. Finalmente, free libera a memória de volta para o sistema.
Mas esta não é a única maneira de escrevê-lo.
Você pode obter exatamente o mesmo resultado trocando p[i] pela aritmética de ponteiro:
Por que isso importa? Porque p[i] é apenas açúcar sintático para *(p+i). O compilador os trata de forma idêntica. Se você estiver trabalhando com sistemas embarcados ou escrevendo loops estreitos onde cada ciclo conta, saber que eles são intercambiáveis ajuda você a ler código legado e a escrever o seu próprio código sem confusão.
Ainda assim, há uma armadilha sutil.
Se você declarar um ponteiro para um tipo de array diretamente – como int (*p)[10] – você estará lidando com uma fera completamente diferente. Esse ponteiro aponta para o array inteiro, não apenas para o primeiro elemento. Incrementá-lo move o ponteiro pelo tamanho de todo o array, não de um único número inteiro. A maioria dos desenvolvedores prefere int * porque é mais simples e flexível.
Quando usar qual abordagem
A escolha entre notação de subscrito e aritmética de ponteiro explícita geralmente se resume à legibilidade e à intenção.
- Use
p[i]quando quiser enfatizar o acesso baseado em índice. É mais claro para a maioria dos leitores. - Use
*(p+i)quando estiver fazendo manipulação de memória de baixo nível ou precisar evitar peculiaridades de decaimento de array em expressões complexas.
Ambas as abordagens requerem um gerenciamento cuidadoso da memória. Esqueça o grátis e você vazará. Chame isso muito cedo e você atingirá um comportamento indefinido. E embora malloc seja direto aqui, sempre verifique se o valor de retorno não é NULL antes de desreferenciar.
“Os ponteiros para arrays são poderosos, mas exigem disciplina. Um passo em falso e você estará lendo lixo — ou pior, corrompendo a memória de outra variável.”
Na prática, a maioria dos desenvolvedores raramente precisa escrever aritmética de ponteiro bruto para matrizes simples. Bibliotecas como std::vector em C++ ou abstrações de nível superior em outras linguagens tratam disso automaticamente. Mas em C? Você está sozinho.
E é por isso que entender a mecânica é importante. Não apenas para passar em entrevistas ou escrever livros didáticos, mas para quando o código for quebrado às 2 da manhã e você precisar saber exatamente o que está acontecendo na memória.

Ao declarar um ponteiro para um array inteiro, você não está criando nada exótico. É apenas um ponteiro padrão para um int. A mágica acontece com malloc. Você aloca um bloco de memória grande o suficiente para quantos números inteiros precisar. O ponteiro então aponta para o primeiro elemento desse bloco.
C não se importa como você o acessa. Você pode usar colchetes como p[5] ou usar aritmética de ponteiro como *(p + 5). O compilador os trata como idênticos. Essa flexibilidade é a razão pela qual os arrays dinâmicos são tão úteis para strings. Você não adivinha o tamanho. Você aloca armazenamento exatamente suficiente para o comprimento da string mais o terminador nulo.
Matrizes de ponteiros vs. Matrizes de estruturas
Por que usar um array de ponteiros quando você poderia usar apenas um array de estruturas? Espaço. Ou melhor, a falta disso.
Considere uma estrutura Rec com três matrizes de caracteres de 81 bytes cada. São 243 bytes por registro. Se você declarar Rec records[10], você reserva instantaneamente 2.430 bytes na memória. Tudo isso. Mesmo se você usar apenas um registro.
Uma série de ponteiros muda a matemática.
O próprio array a contém apenas 10 ponteiros. Em um sistema de 64 bits, isso equivale a 80 bytes. Essa é uma fração da memória necessária para as estruturas completas. A memória para os registros reais permanece sem uso até que você precise dela.
Você pode alocar um único registro sob demanda.
Esse padrão resolve problemas de uso intensivo de memória adiando a alocação. Você só paga pelo que usa. Quando terminar de gravar, você chama free. O ponteiro se torna uma referência pendente, mas a memória é devolvida ao sistema.
Estruturas contendo ponteiros
Estruturas podem conter ponteiros. Isso permite combinar dados de tamanho fixo com dados de tamanho variável no mesmo objeto.
Faça uma entrada no catálogo de endereços. O nome, a cidade e o número de telefone podem ter comprimentos máximos razoáveis. Um comentário, entretanto, pode ser qualquer coisa, desde uma única palavra até um romance. Você não quer desperdiçar espaço alocando um buffer enorme para cada entrada, caso uma pessoa escreva um comentário longo.
A estrutura Addr em si é pequena

Como os campos de comentários tratam registros vazios versus preenchidos
Nem todo registro no banco de dados contém um comentário. Quando um campo é deixado em branco, ele não fica vazio. Ele contém um ponteiro. Especificamente, um ponteiro de 4 bytes que não aponta para nada substancial. O sistema trata esta ausência como um estado válido. O registro ainda está completo. Os metadados estão intactos.
Mas o que acontece quando um usuário digita algo?
A alocação muda. O banco de dados não reserva um buffer fixo para esses comentários. Ele não calcula o comprimento máximo e preenche o restante com bytes nulos. Isso desperdiçaria espaço. Em vez disso, o sistema calcula o comprimento exato da string. Em seguida, ele aloca exatamente essa quantidade de bytes.
Essa alocação dinâmica é eficiente. Evita que a fragmentação inche. Uma nota curta ocupa alguns bytes. Um ensaio longo exige mais. O ponteiro no registro em branco aponta para uma referência nula, mantendo o espaço ocupado mínimo. Os registros com conteúdo se estendem para caber nos dados. Nada é desperdiçado. Nada é forçado.
Esta é a única maneira de armazenar texto? Não. Mas é uma maneira inteligente de equilibrar velocidade e espaço. Você obtém a capacidade de resposta de cabeçalhos de tamanho fixo com a flexibilidade de dados de comprimento variável. É um pequeno detalhe. Um mecânico de baixo nível. Mas isso aumenta quando você gerencia milhões de registros. O banco de dados respira mais facilmente. O uso do disco permanece reduzido.
E o usuário? Eles nunca veem o ponteiro. Eles apenas veem seu comentário. Ou a falta de um. A complexidade está oculta. O armazenamento é otimizado. O resultado é um sistema que parece leve, mesmo quando os dados aumentam.
































