Como as variáveis ​​são armazenadas em um compilador ou intérprete de idioma?

8

Digamos que definimos uma variável em Python.

five = 5

Estrondo. O que eu quero saber é, como isso é armazenado? O compilador ou intérprete apenas o coloca em uma variável como essa?

varname = ["five"]
varval  = [5]

Se é assim que é feito, onde é armazenado? Parece que isso poderia durar para sempre.

baranskistad
fonte

Respostas:

13

Intérprete

Um intérprete trabalhará da maneira que você adivinhou. Em um modelo simples, ele manterá um dicionário com os nomes das variáveis ​​como chaves do dicionário e os valores das variáveis ​​como valor do dicionário. Se a linguagem conhecer o conceito de variáveis ​​visíveis apenas em contextos específicos, o intérprete manterá vários dicionários para refletir os diferentes contextos. O intérprete em si normalmente é um programa compilado, portanto, para seu armazenamento, veja abaixo.

Compilador

(Isso depende muito da linguagem e do compilador e é extremamente simplificado, portanto, serve apenas para dar uma idéia.)

Digamos que temos uma variável global int five = 5. Uma variável global existe apenas uma vez no programa, portanto, o compilador reserva uma área de memória de 4 bytes (tamanho int) em uma área de dados. Ele pode usar um endereço fixo, digamos 1234. No arquivo executável, o compilador coloca as informações de que os quatro bytes iniciados em 1234 são necessários como memória de dados estática, devem ser preenchidos com o número 5 no início do programa e, opcionalmente (por suporte ao depurador) as informações que o local 1234 é chamado fivee contém um número inteiro. Sempre que alguma outra linha de código se refere à variável nomeada five, o compilador lembra que ela é colocada em 1234 e insere uma instrução de leitura ou gravação de memória para o endereço 1234.

Se int six = 6for uma variável local dentro de uma função, ela deve existir uma vez para cada chamada atualmente ativa dessa função (pode haver várias por causa de recursão ou multiencadeamento). Portanto, toda chamada de função acumula espaço suficiente na pilha para armazenar suas variáveis ​​(incluindo quatro bytes para a nossa sixvariável. O compilador decide onde colocar a sixvariável nesse quadro de pilha, talvez a 8 bytes do início do quadro e se lembra dessa posição relativa. Portanto, as instruções que o compilador produz para a função são:

  • avance o ponteiro da pilha em bytes suficientes para todas as variáveis ​​locais da função.

  • armazene o número 6 (valor inicial de six) no local da memória 8 bytes acima do ponteiro da pilha.

  • onde quer que a função se refira six, o compilador insere uma instrução de leitura ou gravação para o local da memória 8 bytes acima do ponteiro da pilha.

  • Quando terminar a função, rebobine o ponteiro da pilha para seu valor antigo.

Mais uma vez, esse é apenas um modelo muito simplificado, que não abrange todos os tipos de variáveis, mas talvez ajude a entender ...

Ralf Kleberhoff
fonte
Fácil de entender para alguém do meu nível de conhecimento. Muito obrigado! Você realmente esclareceu isso!
baranskistad
Fica ainda mais louco quando o compilador não está compilando tudo isso em um binário monolítico, mas em pequenas unidades de compilação (por exemplo, uma por arquivo de origem). Então todo o material externo (como globais) precisa resolver seus endereços posteriormente, por uma ferramenta separada chamada vinculador (que combinará todos esses pequenos arquivos "de objetos" em um binário maior).
Kat
Além disso, se alguém estiver familiarizado com versões mais antigas de C, a natureza de como as variáveis ​​locais funcionam torna óbvio por que C costumava exigir que todas as variáveis ​​locais fossem declaradas na parte superior da função. Mais tarde, decidimos que era possível analisar apenas a função inteira primeiro, para que você pudesse finalmente ter declarações de variáveis ​​onde quisesse.
Kat
Finalmente, talvez também seja relevante que não haja nada impedindo você de usar um dicionário para variáveis ​​locais com um compilador. Isso certamente tornaria a digitação dinâmica muito mais fácil (os tipos dinâmicos não podem ser totalmente armazenados na pilha, pois não têm um tamanho conhecido no momento da compilação). Simplesmente os dicionários são lentos. Aliás, por que a maioria dos idiomas compilados usa digitação estática.
Kat
10

Depende da implementação.

Por exemplo, um compilador C pode manter uma tabela de símbolos durante a compilação. Essa é uma estrutura de dados rica que permite empurrar e estourar escopos, pois cada chave de abertura de instrução composta {potencialmente introduz um novo escopo para novas variáveis ​​locais. Além de lidar com os escopos indo e vindo, ele registra as variáveis ​​declaradas e, para cada uma, inclui os nomes e seus tipos.

Essa estrutura de dados da tabela de símbolos também suporta a busca de informações de uma variável por nome, por exemplo, por seu identificador, e o compilador faz isso quando vincula as informações da variável declarada aos identificadores brutos que vê na análise, portanto isso é bastante precoce durante a compilação.

Em algum momento, o compilador atribui locais às variáveis. Talvez as atribuições de local sejam registradas na mesma estrutura de dados da tabela de símbolos. O compilador pode fazer a atribuição de local diretamente durante a análise, mas é provável que consiga executar um trabalho melhor se esperar não apenas após a análise, mas após a otimização geral.

Em algum momento, para variáveis ​​locais, o compilador atribui um local de pilha ou um registro de CPU (pode ser mais complexo que a variável possa realmente ter vários locais, como um local de pilha para algumas partes do código gerado e uma CPU registre-se para outras seções).

Finalmente, o compilador gera código real: instruções de máquina que referenciam os valores das variáveis ​​diretamente por seus registros de CPU ou localização de pilha atribuída, conforme necessário para executar o código que está sendo compilado. Cada linha de código-fonte é compilada com sua própria série de instruções de código de máquina, portanto, as instruções geradas codificam não apenas as operações (adicionar, subtrair), mas também os locais das variáveis ​​que estão sendo referenciadas.

O código final do objeto que sai do compilador não possui mais nomes e tipos de variáveis; existem apenas locais, locais de pilha ou registros de CPU. Além disso, não há uma tabela de locais, mas esses locais são usados ​​por cada instrução da máquina, sabendo o local onde o valor da variável está armazenado. Sem procurar identificadores no código de tempo de execução, cada bit do código gerado simplesmente conhece a operação a ser executada e os locais a serem usados.

Quando a depuração é ativada durante a compilação, o compilador emitirá uma forma da tabela de símbolos para que, por exemplo, os depuradores conheçam os nomes das variáveis ​​nos vários locais da pilha.

Alguns outros idiomas têm a necessidade de procurar identificadores dinamicamente no tempo de execução, portanto, também podem fornecer algum tipo de tabela de símbolos para dar suporte a essas necessidades.


Os intérpretes têm uma ampla variedade de opções. Eles podem manter uma estrutura de dados semelhante a uma tabela de símbolos para uso durante a execução (além de durante a análise), embora, em vez de atribuir / rastrear um local de pilha, simplesmente armazene o valor da variável, associado à entrada da variável na tabela de símbolos estrutura de dados.

Uma tabela de símbolos talvez seja armazenada no heap e não na pilha (embora o uso da pilha para escopos e variáveis ​​seja certamente possível, além disso, ele pode imitar uma pilha no heap para obter a vantagem amigável do cache de compactar os valores da variável perto de cada outro), portanto, um intérprete provavelmente está usando memória heap para armazenar os valores da variável, enquanto um compilador usa locais de pilha. De um modo geral, o intérprete também não tem a liberdade de usar registros da CPU como armazenamento para os valores das variáveis, pois os registros da CPU estão ocupados executando as linhas de código do próprio interpretador ...

Erik Eidt
fonte
-3

A melhor maneira de entender em que seu código está sendo compilado é compilá-lo no assembly . O código de montagem está mais próximo das instruções do processador que estão sendo executadas.

Samuel
fonte
1
Parece-me que o OP não está perguntando sobre o código compilado, mas o que está acontecendo durante a compilação enquanto o compilador está analisando o código.
user1118321