Para realmente entender por que os ponteiros se comportam dessa maneira, você precisa parar de pensar em termos de variáveis abstratas e começar a visualizar o hardware. É um modelo simples, mas é a base de tudo, desde falhas de segurança até vulnerabilidades de segurança. Se você não sabe o básico sobre bits, bytes e palavras, provavelmente deveria fazer uma pausa aqui e revisar isso primeiro. O restante desta explicação pressupõe que você saiba a diferença entre um byte e um bit.
Seu computador possui memória ou RAM (memória de acesso aleatório). Você pode ter 16, 32 ou 64 gigabytes instalados. Isto não é apenas armazenamento; é o espaço de trabalho. Ele contém o código que sua CPU está executando no momento e os dados que esses programas estão manipulando neste exato segundo. Pense na RAM como uma enorme matriz linear de bytes.
Cada byte nessa matriz possui um endereço. O primeiro byte é o endereço 0. O próximo é 1. Depois, 2, 3 e assim por diante. Esses endereços funcionam exatamente como índices em um array de programação padrão. Como a CPU pode saltar para qualquer endereço instantaneamente, ela é chamada de “memória de acesso aleatório”. Não é necessário ler sequencialmente. Ele pode pegar o que precisar, sempre que precisar. Quando o compilador precisa armazenar um tipo de dados maior, ele apenas captura um bloco de bytes contíguos. Um número de ponto flutuante padrão, por exemplo, geralmente ocupa 4 bytes contíguos.
Vejamos uma declaração global em C:
Para você, esta é uma variável chamada f que contém um float. Para o computador, este é um comando para reservar 4 bytes específicos no array de memória. Digamos que o compilador atribua esses bytes ao local de memória 248.440.
Quando você escreve:
O compilador não pensa em “atualizar variável f”. Ele pensa em “carregar o valor 3,14 no endereço de memória 248.440”. A abstração é tênue. Nos bastidores, tudo gira em torno dos endereços e dos valores contidos nesses endereços.
Essa natureza mecânica da memória leva a alguns efeitos colaterais desagradáveis. Considere este trecho de código C:
Você pode esperar que s e t contenham 0:0, 1:1, 2:2, 3:3. E u deve permanecer 0. Em vez disso, a saída será semelhante a esta:
Espere. O que aconteceu com t[0]? E por que u agora tem 5 anos?
O problema é um clássico estouro de buffer. Observe atentamente a condição do loop: i<=4. O array t é declarado como t[4], o que significa que tem índices 0, 1, 2 e 3. Quando i atinge 4, o código escreve em t[4]. Esse índice não existe no espaço alocado para t. Ele grava um elemento após o final do array.
Como a memória é contígua, o computador coloca s, t e u um ao lado do outro no heap ou pilha.
Quando você escreve além do final de um array, você não trava imediatamente. Você sobrescreve tudo o que está no slot de memória próximo a ele.
Neste caso específico, escrever em t[4] substitui o local de memória que contém u (que era originalmente 0). Mas por que você

Escreva para s[4]. Esse índice não existe. Mas o computador não se importa. Ele não verifica se você está pisando nos próprios pés. Ele apenas calcula o endereço. E esse endereço acaba em t[0].
Você acha que está escrevendo para s. Na verdade, você está escrevendo para t. O sistema executa o comando sem piscar. A lógica é falha. O programa corrompe dados. É silencioso até que não seja.
Agora tente algo pior.
s[1000000] = 5;
Você está gravando na memória que seu programa não possui. As consequências dependem inteiramente do sistema operacional que você está executando. Em sistemas protegidos como UNIX, Windows NT ou Windows 98, o sistema operacional detecta essa violação imediatamente. Ele encerra o programa. Parada difícil. Ele protege o resto do sistema do seu erro.
Sistemas mais antigos como o Windows 3.1 ou o Mac OS clássico não têm esse luxo. Eles não sabem o que você está fazendo. Você acaba sobrescrevendo códigos ou variáveis em outro aplicativo. O resultado? Uma falha. Um acidente. Ou pior, uma falha em todo o sistema que parece aleatória, mas na verdade é o resultado direto de um acesso não verificado à memória.
Na memória, variáveis como i, s, t e u ficam próximas umas das outras em endereços específicos. Eles são vizinhos. Se você escrever além do limite de um, você passará para o próximo. O computador faz exatamente o que você disse. Simplesmente não importa que você tenha boas intenções.
O perigo do acesso não verificado ao array
C e C++ não realizam verificação de intervalo. Eles presumem que você sabe o que está fazendo. Isso significa que você deve prestar muita atenção aos intervalos de array. Se você lê ou escreve fora dos limites, o comportamento é defeituoso. Imprevisível. Perigoso.
Essa falta de segurança é a razão pela qual erros de ponteiro não inicializados são tão comuns em C. Considere este código:
O compilador imprime o valor em p e o endereço de i. Inicialmente, p contém lixo ou zero. O endereço de i geralmente é um número grande. Por exemplo, você pode ver:
Depois de p = &i, p contém o endereço de i. Simples.
Agora veja isto:
Isso imprime o valor para o qual p aponta. Mas p não foi inicializado. Aponta para o endereço 0 ou algum bit aleatório de memória. O resultado é quase sempre uma falha de segmentação. Um erro de tempo de execução. Você está tentando acessar uma memória que não pertence a você.
Ponteiros sob uma nova luz
Depois de compreender esses riscos, os indicadores mudam. Elas não são apenas variáveis. Eles são endereços diretos para locais de memória.
Faça este programa:
Aqui está o que está acontecendo:
Como os ponteiros armazenam endereços de memória
Quando você declara uma variável inteira como i, ela ocupa 4 bytes de espaço na RAM. Um ponteiro, vamos chamá-lo de p, também ocupa 4 bytes. Esse tamanho é válido para a grande maioria dos sistemas atualmente em circulação, onde os endereços de memória têm 32 bits. A indústria está mudando lentamente para o endereçamento de 64 bits, mas, por enquanto, o tamanho de 4 bytes é o padrão.
Pense em i como uma casa. Tem um endereço específico – digamos, 248.440. O ponteiro p não é a casa em si. É um pedaço de papel com esse endereço escrito. Ao atribuir p = &i, você está escrevendo 248.440 nesse papel.
Essa distinção é crítica para entender como os ponteiros funcionam em C. O ponteiro não contém o valor de i. Ele contém o local onde i mora. Se você desreferenciar p (usando p *), você irá para esse endereço e lerá o valor. Nesse sentido, p e i * são funcionalmente idênticos. Eles apontam para os mesmos dados.
Visualizando o endereço no código
Você pode verificar esse comportamento com uma simples instrução print.
printf("%d",p);
Este comando não imprime o valor de i. Ele imprime o número escrito no papel. A saída é o endereço de memória real de i.
Por que isso importa? Porque permite passar locais em vez de cópias de dados. Você pode manipular a memória diretamente. Você pode vincular estruturas. Você pode gerenciar a memória heap com eficiência. O ponteiro é uma referência. A variável é o conteúdo. Confundir os dois leva a bugs. Compreender a diferença leva ao controle.
Alguns desenvolvedores tratam os ponteiros como mágica. Eles não são. Eles são apenas números. Endereços de memória são inteiros. Trate-os como tal.

















