Dado o fundamento, agora iremos explanar como funciona o armazenamento destas informações na memória com um copy/paste deste artigo do HowStuffWorks.
Todos os computadores têm memória, também conhecida por RAM (memória de acesso aleatório). Por exemplo, seu computador pode ter 16, 32 ou 64 megabytes de memória RAM instalados (hoje em dia são gigabytes). A memória RAM mantém os programas que atualmente estão em execução no computador, junto aos dados que estão sendo manipulados (suas variáveis e estruturas de dados). A memória pode ser vista como uma matriz de bytes. Nesta matriz, cada local de memória tem seu próprio endereço. O endereço do primeiro byte é 0, seguido por 1, 2, 3, e assim sucessivamente. O endereço de memória atua como os índices de uma matriz normal. O computador pode acessar qualquer endereço na memória, a qualquer momento (por isso o nome “memória de acesso aleatório”). Ele também pode agrupar os bytes necessários para formar matrizes, variáveis e estruturas maiores. Por exemplo, uma variável de ponto flutuante consome 4 bytes contínuos em memória. Você pode fazer a seguinte declaração global em um programa:
float f; /* C*/
var f: Single; { Pascal }
Esta instrução diz: “Declare um local denominado f que possa manter um valor de ponto flutuante”. Quando o programa é executado, o computador reserva espaço para a variável f em algum lugar na memória. Aquele local tem um endereço fixo no espaço de memória, assim:
A variável f consome 4 bytes de RAM na memória.
Este local tem um endereço específico, neste caso 248,440.Enquanto você pensa na variável f, o computador pensa em um endereço específico na memória (por exemplo, 248,440). Assim, ao criar uma instrução como esta:
f = 3.14; /* C */
f := 3.14; { Pascal }
O compilador poderia traduzir isso como: “Carregar o valor 3.14 no local de memória 248,440″. O computador está sempre pensando na memória em termos de endereço e valores para tais endereços.
A propósito, existem vários efeitos colaterais interessantes na forma com a qual seu computador gerencia a memória. Por exemplo, suponhamos que você incluiu a seguinte codificação em um de seus programas:
for (i=0; i<=4; i++)
{
s[i] = i;
t[i] = i;
}
Printf("s:t\n");
for (i=0; i<=4; i++)
printf("%d:%d\n", , s[i], t[i]);
printf("u = %d\n", u);
O paralelo em Pascal / Delphi, temos:
{$APPTYPE CONSOLE}
uses
SysUtils;
var
i: Integer;
s, t: array [0..3] of Integer;
u: Integer;
begin
u := 0;
for i := 0 to 4 do
begin
s[i] := i;
t[i] := i;
end;
WriteLn(’s:t’);
for i := 0 to 4 do
WriteLn(Format(‘%d:%d’, [s[i], t[i]]));
WriteLn(Format(‘u = %d’, [u]));
ReadLn;
end.
O resultado que você vê no programa provavelmente será algo assim:
s:t 0:4 1:1 2:2 3:3 4:4 u = 4
Por que t[0] e u estão incorretos? Se olhar cuidadosamente o código, você verá que os loops for estão escrevendo um elemento além do final de cada matriz. Na memória, as matrizes são colocadas lado a lado, como mostrado abaixo:
Logo, ao tentar escrever para s[4], que não existe, o sistema escreve para t[0], uma vez que t[0] é onde s[4] deveria estar. Ao escrever para t[4], na verdade você está escrevendo em u. No que se refere ao computador, s[4] é simplesmente um endereço e pode ser escrito. Como você pode ver, mesmo que o computador execute o programa, ele não está correto ou válido. O programa corrompe a matriz t no processo de execução. A execução da seguinte instrução pode resultar em conseqüências mais graves:
s[1000000] = 5; /* C */
s[1000000] := 5; { Pascal }
O local s[1000000] provavelmente está fora do espaço de memória de seu programa. Em outras palavras, você está escrevendo na memória que seu programa não possui. Em um sistema com espaços protegidos de memória (UNIX, Windows 98/NT), este tipo de instrução fará com que o sistema encerre a execução do programa. Em outros sistemas (Windows 3.1, Mac), todavia, o sistema não sabe o que você está fazendo. Você acaba danificando o código ou as variáveis em outro aplicativo. O efeito da violação pode variar de nenhum a uma tremenda pane do sistema. Na memória, i, s, t e u são todos colocados próximos uns aos outros em endereços específicos. Portanto, se você escrever além dos limites de uma variável, o computador fará o que foi solicitado, porém acabará corrompendo outro local de memória.
As linguagens C e C++ não executam qualquer tipo de verificação de extensão ao acessar um elemento na matriz. É essencial que você, como programador, preste atenção às extensões de matriz e respeite seus limites. A leitura ou escrita não-intencional fora dos limites da matriz quase sempre leva ao comportamento errôneo do programa.
O Pascal / Delphi por padrão também não executa qualquer tipo de verificação de extensão ao acessar um elemento na matriz. Mas isto pode ser modificado com a diretiva $R. Se no código anterior acrescentarmos {$R+} no início, ao acessar o elemento 4 o software lança a excessão ERangeError com a mensagem Range check error. Mas não é recomendo pois torna a execução mais lenta do código.
Como outro exemplo, tente o seguinte:
int main()
{
int i,j;
int *p;
/* um ponteiro para um inteiro */
printf("%d %d\n", p, &i);
p = &i;
printf("%d %d\n", p, &i);
return 0;
}
Em Pascal / Delphi temos:
{$APPTYPE CONSOLE}
uses
SysUtils;
type
PInteger = ^Integer;
var
i: Integer;
p: PInteger;
begin
WriteLn(Format(‘%p %p’, [p, @i]));
p := @i;
WriteLn(Format(‘%p %p’, [p, @i]));
ReadLn;
end.
Este código diz ao compilador para imprimir o endereço contido em p, junto com o endereço de i. A variável p começa com um valor estranho ou com 0. O endereço de i geralmente é um valor alto. Por exemplo, quando executei este código, recebi o seguinte resultado:
7FFDD000 0040A600 0040A600 0040A600
que significa que o endereço de i é 0040A600 (em hexadecimal). Após executar a instrução p = &i; ou p := @i;, p contém o endereço de i. Tente também:
Em Pascal / Delphi temos:
{$APPTYPE CONSOLE}
uses
SysUtils;
type
PInteger = ^Integer;
var
p: PInteger;
begin
WriteLn(Format(‘%d’, [p^]));
ReadLn;
end.
Este código diz ao compilador para imprimir o valor para o qual p aponta. Porém, p ainda não foi inicializado. Ele contém o endereço 0 ou algum endereço aleatório. Na maioria dos casos, resulta em uma falha de segmentação (ou algum outro erro no tempo de execução), o que significa que você usou um ponteiro que aponta para uma área inválida da memória. Quase sempre, um ponteiro não inicializado ou um endereço de ponteiro inválido é a causa das falhas de segmentação.
Dito isso, agora podemos ver os ponteiros por uma ótica totalmente diferente. Veja este programa, por exemplo:
int main()
{
int i;
int *p;
/* um ponteiro para um inteiro*/
p = &i;
*p=5;
printf("%d %d\n", i, *p);
return 0;
}
Em Pascal / Delphi temos:
{$APPTYPE CONSOLE}
uses
SysUtils;
type
PInteger = ^Integer;
var
i: Integer;
p: PInteger;
begin
p := @i;
p^ := 5;
WriteLn(Format(‘%d %d’, [i, p^]));
ReadLn;
end.
Eis o que está acontecendo:
A variável i consome 4 bytes de memória. O ponteiro p também consome 4 bytes (na maioria das máquinas atuais, um ponteiro consome 4 bytes de memória; os endereços de memória tem 32 bits de extensão na maioria das CPUs, embora exista uma tendência de crescimento para o endereçamento de 64 bits). O local de i tem um endereço específico, neste caso 248,440. O ponteiro p contém aquele endereço, uma vez que você disse que p = &i; ou p := @i;. As variáveis *p ou p^ e i são, portanto, equivalentes.
O ponteiro p literalmente contém o endereço de i. Você pode dizer algo assim em um programa:
printf(”%d”, p); /* C */
Format(’%p’, p); { Pascal }
o resultado é o endereço corrente da variável i.
Pra uma pessoa que nunca viu Ponteiro, com esse conteúdo continuará ainda sem compreender.Seja mais claro, mais simples.