Quando um computador armazena uma variável, quando um programa precisa obter o valor da variável, como o computador sabe onde procurar na memória o valor dessa variável?
Quando um computador armazena uma variável, quando um programa precisa obter o valor da variável, como o computador sabe onde procurar na memória o valor dessa variável?
Respostas:
Eu sugiro que você olhe para o maravilhoso mundo da Construção de Compiladores! A resposta é que é um processo um pouco complicado.
Para tentar lhe dar uma intuição, lembre-se de que os nomes de variáveis existem apenas para o bem do programador. O computador acabará transformando tudo em endereços no final.
As variáveis locais são (geralmente) armazenadas na pilha: ou seja, fazem parte da estrutura de dados que representa uma chamada de função. Podemos determinar a lista completa de variáveis que uma função (talvez) usará, observando essa função, para que o compilador possa ver quantas variáveis ele precisa para essa função e quanto espaço cada variável ocupa.
Há um pouco de mágica chamada ponteiro da pilha, que é um registro que sempre armazena o endereço de onde a pilha atual é iniciada.
Cada variável recebe um "deslocamento de pilha", que é o local na pilha em que está armazenada. Então, quando o programa precisar acessar uma variável x
, o compilador será substituído x
por STACK_POINTER + x_offset
, para obter o local físico real em que está armazenado na memória.
Observe que, é por isso que você recebe um ponteiro de volta quando usa malloc
ou new
em C ou C ++. Você não pode determinar exatamente onde está um valor alocado na pilha, portanto, é necessário manter um ponteiro para ele. Esse ponteiro estará na pilha, mas apontará para o heap.
Os detalhes da atualização de pilhas para chamadas e retornos de funções são complicados; portanto, recomendo The Dragon Book ou The Tiger Book, se você estiver interessado.
Quando um computador armazena uma variável, quando um programa precisa obter o valor da variável, como o computador sabe onde procurar na memória o valor dessa variável?
O programa diz isso. Os computadores não têm um conceito nativo de "variáveis" - isso é algo totalmente de linguagem de alto nível!
Aqui está um programa em C:
int main(void)
{
int a = 1;
return a + 3;
}
e aqui está o código de montagem que ele compila: (comentários começando com ;
)
main:
; {
pushq %rbp
movq %rsp, %rbp
; int a = 1
movl $1, -4(%rbp)
; return a + 3
movl -4(%rbp), %eax
addl $3, %eax
; }
popq %rbp
ret
Para "int a = 1;" a CPU vê a instrução "armazene o valor 1 no endereço (valor do registrador rbp, menos 4)". Ele sabe onde armazenar o valor 1 porque o programa o informa.
Da mesma forma, a próxima instrução diz "carrega o valor no endereço (valor do registrador rbp, menos 4) no registrador eax". O computador não precisa saber sobre coisas como variáveis.
%rsp
é o ponteiro da pilha da CPU. %rbp
é um registro que se refere ao bit da pilha usado pela função atual. O uso de dois registradores simplifica a depuração.
Quando o compilador ou intérprete encontra a declaração de uma variável, decide qual endereço será usado para armazenar essa variável e, em seguida, registra o endereço em uma tabela de símbolos. Quando referências subseqüentes a essa variável são encontradas, o endereço da tabela de símbolos é substituído.
O endereço registrado na tabela de símbolos pode ser um deslocamento de um registrador (como o ponteiro da pilha), mas esse é um detalhe da implementação.
Os métodos exatos dependem do que você está falando especificamente e de quão profundo você quer ir. Por exemplo, armazenar arquivos em um disco rígido é diferente de armazenar algo na memória ou algo em um banco de dados. Embora os conceitos sejam semelhantes. E como você faz isso no nível de programação é uma explicação diferente da maneira como o computador faz no nível de E / S.
A maioria dos sistemas usa algum tipo de mecanismo de diretório / índice / registro para permitir que o computador encontre e acesse os dados. Esse índice / diretório conterá uma ou mais chaves e o endereço em que os dados estão realmente localizados (seja disco rígido, RAM, banco de dados etc.).
Exemplo de programa de computador
Um programa de computador pode acessar a memória de várias maneiras. Normalmente, o sistema operacional fornece ao programa um espaço de endereço e o programa pode fazer o que quiser com esse espaço de endereço. Ele pode gravar diretamente em qualquer endereço dentro de seu espaço de memória e acompanhar como deseja. Às vezes, isso varia de acordo com a linguagem de programação e o sistema operacional, ou mesmo de acordo com as técnicas preferidas de um programador.
Como mencionado em algumas das outras respostas, a codificação ou programação exata usada difere, mas geralmente nos bastidores, ela usa algo como uma pilha. Possui um registro que armazena o local da memória onde a pilha atual é iniciada e, em seguida, um método para saber onde está uma função ou variável nessa pilha.
Em muitas linguagens de programação de nível superior, ele cuida de tudo isso para você. Tudo o que você precisa fazer é declarar uma variável e armazenar algo nessa variável, e ela cria as pilhas e matrizes necessárias nos bastidores para você.
Mas, considerando a versatilidade da programação, não há realmente uma resposta, pois um programador pode optar por escrever diretamente em qualquer endereço dentro do espaço alocado a qualquer momento (assumindo que ele esteja usando uma linguagem de programação que permita isso). Depois, ele poderia armazenar sua localização em uma matriz ou até mesmo codificá-la no programa (ou seja, a variável "alfa" é sempre armazenada no início da pilha ou sempre nos primeiros 32 bits de memória alocada).
Sumário
Então, basicamente, deve haver algum mecanismo nos bastidores que informe ao computador onde os dados são armazenados. Uma das maneiras mais populares é algum tipo de índice / diretório que contém chaves e o endereço de memória. Isso é implementado de todos os tipos e geralmente é encapsulado pelo usuário (e às vezes até encapsulado pelo programador).
Referência: como os computadores lembram onde armazenam as coisas?
Sabe por causa de modelos e formatos.
O programa / função / computador não sabe realmente onde está. Apenas espera que algo esteja em um determinado lugar. Vamos usar um exemplo.
class simpleClass{
public:
int varA=58;
int varB=73;
simpleClass* nextObject=NULL;
};
Nossa nova classe 'simpleClass' contém 3 variáveis importantes - dois inteiros que podem conter alguns dados quando precisamos deles e um ponteiro para outro 'objeto simpleClass'. Vamos supor que estamos em uma máquina de 32 bits por uma questão de simplicidade. 'gcc' ou outro compilador 'C' criaria um modelo para trabalharmos para alocar alguns dados.
Tipos simples
Em primeiro lugar, quando se usa uma palavra-chave para um tipo simples como 'int', uma anotação é feita pelo compilador na seção '.data' ou '.bss' do arquivo executável, para que, quando executado pelo sistema operacional, os dados sejam disponível para o programa. A palavra-chave 'int' alocaria 4 bytes (32 bits), enquanto uma 'int longa' alocaria 8 bytes (64 bits).
Às vezes, de maneira célula a célula, uma variável pode vir logo após a instrução que deveria carregá-la na memória, portanto seria semelhante na pseudo-montagem:
...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...
Isso terminaria com o valor '5' no EAX e no EBX.
Enquanto o programa é executado, todas as instruções são executadas, exceto o '5' desde o carregamento imediato faz referência a ela e faz a CPU pular sobre ela.
A desvantagem desse método é que ele é realmente realmente prático para constantes, pois seria impraticável manter matrizes / buffers / strings no meio do seu código. Portanto, geralmente, a maioria das variáveis é mantida nos cabeçalhos do programa.
Se alguém precisasse acessar uma dessas variáveis dinâmicas, poderia tratar o valor imediato como se fosse um ponteiro:
...
clear register EAX
clear register EBX
load the immediate value into EAX
0x0AF2CE66 (Let's say this is the address of a cell containing '5')
load the value pointed to by EAX into EBX
...
Isso terminaria com o valor '0x0AF2CE66' no registro EAX e o valor de '5' no registro EBX. Também é possível adicionar valores em registradores juntos, para que possamos encontrar elementos de uma matriz ou string usando esse método.
Outro ponto importante é que é possível armazenar valores ao usar endereços de maneira semelhante, para que você possa referenciar os valores nessas células posteriormente.
Tipos complexos
Se fizermos dois objetos dessa classe:
simpleClass newObjA;
simpleClass newObjB;
então, podemos atribuir um ponteiro para o segundo objeto no campo disponível para ele no primeiro objeto:
newObjA.nextObject=&newObjB;
Agora, o programa pode esperar encontrar o endereço do segundo objeto no campo de ponteiro do primeiro objeto. Na memória, isso seria algo como:
newObjA: 58
73
&newObjB
...
newObjB: 58
73
NULL
Um fato muito importante a ser observado aqui é que 'newObjA' e 'newObjB' não têm nomes quando são compilados. São apenas lugares onde esperamos que alguns dados estejam. Portanto, se adicionarmos 2 células a & newObjA, encontraremos a célula que atua como 'nextObject'. Portanto, se sabemos o endereço de 'newObjA' e onde a célula 'nextObject' é relativa a ele, podemos saber o endereço de 'newObjB':
...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX
Isso terminaria com '2 + & newObjA' em 'EAX' e '& newObjB' em 'EBX'.
Modelos / Formatos
Quando o compilador compila a definição de classe, está realmente compilando uma maneira de criar um formato, uma maneira de gravar em um formato e uma maneira de ler de um formato.
O exemplo dado acima é um modelo para uma lista vinculada individualmente com duas variáveis 'int'. Esses tipos de construções são muito importantes para alocação dinâmica de memória, juntamente com árvores binárias e n-árias. As aplicações práticas de árvores n-árias seriam sistemas de arquivos compostos por diretórios apontando para arquivos, diretórios ou outras instâncias reconhecidas pelos drivers / sistema operacional.
Para acessar todos os elementos, pense em um verme avançando para cima e para baixo na estrutura. Dessa forma, o programa / função / computador não sabe nada, apenas executa instruções para mover os dados.