Comment les ordinateurs se souviennent-ils de l'endroit où ils stockent les choses?

32

Lorsqu'un ordinateur stocke une variable, lorsqu'un programme doit obtenir la valeur de la variable, comment l'ordinateur sait-il où chercher en mémoire la valeur de cette variable?

MCMastery
la source
17
Ce n'est pas le cas; "l'ordinateur" est complètement inconscient. Nous devons coder en dur toutes les adresses. (Ce qui simplifie un peu, mais pas trop.)
Raphael
1
@Raphael: Généralisons cela à "nous devons coder en dur les adresses de base".
phresnel
Chaque fois que vous déclarez une variable, le programme responsable de l'exécution de votre code inclut le nom de la variable avec son adresse dans une table de hachage (aka namespace). Je suggère de lire le livre "Structure et mise en œuvre des programmes informatiques (SICP) pour bien connaître ces petits détails.
Abhirath Mahipal
Votre programme source utilise une variable. Le compilateur ou l'interpréteur décide comment l'implémenter: il génère des instructions à exécuter par l'ordinateur et doit s'assurer que ces instructions récupèrent les valeurs des endroits où les instructions précédentes les ont stockées.
PJTraill
1
@AbhirathMahipal: une variable n'a pas besoin d'avoir une adresse au moment de la compilation ou même au moment de l'exécution; «Namespace» est un concept de langage tandis qu'une table (hachée ou non) est un détail d'implémentation; le nom need nod persiste dans le programme lors de son exécution.
PJTraill du

Réponses:

31

Je vous suggère de regarder dans le monde merveilleux de la construction de compilateurs! La réponse est que c'est un processus un peu compliqué.

Pour essayer de vous donner une intuition, n'oubliez pas que les noms de variables sont purement là pour le programmeur. L'ordinateur transformera finalement tout en adresses à la fin.

Les variables locales sont (généralement) stockées sur la pile: c'est-à-dire qu'elles font partie de la structure de données qui représente un appel de fonction. Nous pouvons déterminer la liste complète des variables qu'une fonction utilisera (peut-être) en regardant cette fonction, afin que le compilateur puisse voir le nombre de variables dont il a besoin pour cette fonction et combien d'espace chaque variable prend.

Il y a un peu de magie appelée le pointeur de pile, qui est un registre qui stocke toujours l'adresse d'où commence la pile actuelle.

Chaque variable reçoit un "décalage de pile", qui est l'endroit où dans la pile elle est stockée. Ensuite, lorsque le programme doit accéder à une variable x, le compilateur remplace xpar STACK_POINTER + x_offset, pour obtenir l'emplacement physique réel dans lequel il est stocké en mémoire.

Notez que, c'est pourquoi vous obtenez un pointeur lorsque vous utilisez mallocou newen C ou C ++. Vous ne pouvez pas déterminer où se trouve exactement en mémoire une valeur allouée au tas, vous devez donc garder un pointeur vers elle. Ce pointeur sera sur la pile, mais il pointera vers le tas.

Les détails de la mise à jour des piles pour les appels de fonction et les retours sont compliqués, donc je recommanderais The Dragon Book ou The Tiger Book si vous êtes intéressé.

jmite
la source
24

Lorsqu'un ordinateur stocke une variable, lorsqu'un programme doit obtenir la valeur de la variable, comment l'ordinateur sait-il où chercher en mémoire la valeur de cette variable?

Le programme le raconte. Les ordinateurs n'ont pas nativement un concept de "variables" - c'est tout à fait une chose de langage de haut niveau!

Voici un programme C:

int main(void)
{
    int a = 1;
    return a + 3;
}

et voici le code assembleur qu'il compile: (commentaires commençant par ;)

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

Pour "int a = 1;" la CPU voit l'instruction "stocker la valeur 1 à l'adresse (valeur du registre rbp, moins 4)". Il sait où stocker la valeur 1 car le programme le lui dit.

De même, l'instruction suivante dit "charger la valeur à l'adresse (valeur du registre rbp, moins 4) dans le registre eax". L'ordinateur n'a pas besoin de connaître des choses comme les variables.

user253751
la source
2
Pour connecter cela à la réponse de jmite, %rspc'est le pointeur de pile du CPU. %rbpest un registre qui fait référence au bit de la pile utilisé par la fonction courante. L'utilisation de deux registres simplifie le débogage.
MSalters
2

Lorsque le compilateur ou l'interpréteur rencontre la déclaration d'une variable, il décide quelle adresse il utilisera pour stocker cette variable, puis enregistre l'adresse dans une table de symboles. Lorsque des références ultérieures à cette variable sont rencontrées, l'adresse de la table des symboles est remplacée.

L'adresse enregistrée dans la table des symboles peut être un décalage par rapport à un registre (tel que le pointeur de pile) mais c'est un détail d'implémentation.

Scott Cooper
la source
0

Les méthodes exactes dépendent de ce dont vous parlez précisément et de la profondeur que vous souhaitez atteindre. Par exemple, le stockage de fichiers sur un disque dur est différent du stockage de quelque chose en mémoire ou du stockage de quelque chose dans une base de données. Bien que les concepts soient similaires. Et la façon dont vous le faites au niveau de la programmation est une explication différente de la façon dont un ordinateur le fait au niveau des E / S.

La plupart des systèmes utilisent une sorte de mécanisme de répertoire / index / registre pour permettre à l'ordinateur de trouver et d'accéder aux données. Cet index / répertoire contiendra une ou plusieurs clés et l'adresse dans laquelle les données se trouvent réellement (que ce soit le disque dur, la RAM, la base de données, etc.).

Exemple de programme informatique

Un programme informatique peut accéder à la mémoire de différentes manières. En règle générale, le système d'exploitation donne au programme un espace d'adressage et le programme peut faire ce qu'il veut avec cet espace d'adressage. Il peut écrire directement à n'importe quelle adresse dans son espace mémoire, et il peut suivre ce qu'il veut. Cela varie parfois selon le langage de programmation et le système d'exploitation, ou même selon les techniques préférées d'un programmeur.

Comme mentionné dans certaines des autres réponses, le codage ou la programmation exacte utilisé diffère, mais généralement en arrière-plan, il utilise quelque chose comme une pile. Il a un registre qui stocke l'emplacement de mémoire où la pile actuelle commence, puis une méthode pour savoir où se trouve une fonction ou une variable dans cette pile.

Dans de nombreux langages de programmation de niveau supérieur, il s'occupe de tout cela pour vous. Tout ce que vous avez à faire est de déclarer une variable et de stocker quelque chose dans cette variable, et cela crée pour vous les piles et les tableaux nécessaires en arrière-plan.

Mais compte tenu de la polyvalence de la programmation, il n'y a pas vraiment de réponse, car un programmeur peut choisir d'écrire directement à n'importe quelle adresse dans son espace alloué à tout moment (en supposant qu'il utilise un langage de programmation qui le permet). Ensuite, il pourrait stocker son emplacement dans un tableau, ou même le coder en dur dans le programme (c'est-à-dire que la variable "alpha" est toujours stockée au début de la pile ou toujours stockée dans les 32 premiers bits de la mémoire allouée).

Sommaire

Donc, fondamentalement, il doit y avoir un mécanisme dans les coulisses qui indique à l'ordinateur où les données sont stockées. L'un des moyens les plus populaires est une sorte d'index / répertoire contenant des clés et l'adresse mémoire. Ceci est implémenté de toutes sortes de façons et est généralement encapsulé par l'utilisateur (et parfois même encapsulé par le programmeur).

Référence: Comment les ordinateurs se souviennent-ils de l'endroit où ils stockent les choses?

Scott M. Stolz
la source
0

Il le sait à cause des modèles et des formats.

Le programme / la fonction / l'ordinateur ne sait pas vraiment où se trouve quoi que ce soit. Il s'attend juste à ce que quelque chose soit à un certain endroit. Prenons un exemple.

class simpleClass{
    public:
        int varA=58;
        int varB=73;
        simpleClass* nextObject=NULL;
};

Notre nouvelle classe «simpleClass» contient 3 variables importantes - deux entiers qui peuvent contenir des données lorsque nous en avons besoin et un pointeur vers un autre «objet simpleClass». Supposons que nous soyons sur une machine 32 bits pour des raisons de simplicité. 'gcc' ou un autre compilateur 'C' ferait un modèle avec lequel nous pourrions allouer des données.

Types simples

Premièrement, quand on utilise un mot-clé pour un type simple comme 'int', une note est faite par le compilateur dans la section '.data' ou '.bss' du fichier exécutable de sorte que quand il est exécuté par le système d'exploitation, les données sont disponible au programme. Le mot clé 'int' allouerait 4 octets (32 bits), tandis qu'un 'long int' allouerait 8 octets (64 bits).

Parfois, de manière cellule par cellule, une variable peut apparaître juste après l'instruction qui est censée la charger en mémoire, elle ressemblerait donc à ceci dans un pseudo-assemblage:

...
clear register EAX
clear register EBX
load the immediate (next) value into EAX
5
copy the value in register EAX to register EBX
...

Cela se terminerait par la valeur «5» dans EAX et EBX.

Pendant que le programme s'exécute, chaque instruction est exécutée, à l'exception du «5», car la charge immédiate y fait référence et oblige la CPU à la sauter.

L'inconvénient de cette méthode est qu'elle n'est vraiment pratique que pour les constantes, car il serait peu pratique de conserver les tableaux / tampons / chaînes au milieu de votre code. Donc, généralement, la plupart des variables sont conservées dans les en-têtes de programme.

Si l'on avait besoin d'accéder à l'une de ces variables dynamiques, alors on pourrait traiter la valeur immédiate comme s'il s'agissait d'un pointeur:

...
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
...

Cela se terminerait par la valeur «0x0AF2CE66» dans le registre EAX et la valeur de «5» dans le registre EBX. On peut également ajouter des valeurs dans les registres ensemble, afin que nous puissions trouver des éléments d'un tableau ou d'une chaîne en utilisant cette méthode.

Un autre point important est que l'on peut stocker des valeurs lors de l'utilisation d'adresses d'une manière similaire, afin que l'on puisse référencer les valeurs à ces cellules plus tard.

Types complexes

Si nous faisons deux objets de cette classe:

simpleClass newObjA;
simpleClass newObjB;

alors nous pouvons assigner un pointeur au deuxième objet au champ disponible pour lui dans le premier objet:

newObjA.nextObject=&newObjB;

Maintenant, le programme peut s'attendre à trouver l'adresse du deuxième objet dans le champ de pointeur du premier objet. En mémoire, cela ressemblerait à quelque chose comme:

newObjA:    58
            73
            &newObjB
            ...
newObjB:    58
            73
            NULL

Un fait très important à noter ici est que «newObjA» et «newObjB» n'ont pas de noms lorsqu'ils sont compilés. Ce ne sont que des endroits où nous attendons des données. Donc, si nous ajoutons 2 cellules à & newObjA, nous trouvons la cellule qui agit comme «nextObject». Par conséquent, si nous connaissons l'adresse de 'newObjA' et où la cellule 'nextObject' lui est relative, alors nous pouvons connaître l'adresse de 'newObjB':

...
load the immediate value into EAX
&newObjA
add the immediate value to EAX
2
load the value in EAX into EBX

Cela se terminerait par «2 + & newObjA» dans «EAX» et «& newObjB» dans «EBX».

Modèles / Formats

Lorsque le compilateur compile la définition de classe, il s'agit vraiment de compiler un moyen de créer un format, un moyen d'écrire dans un format et un moyen de lire à partir d'un format.

L'exemple donné ci-dessus est un modèle pour une liste à liaison unique avec deux variables «int». Ces types de constructions sont très importants pour l'allocation dynamique de mémoire, ainsi que les arbres binaires et n-aires. Les applications pratiques des arbres n-aires seraient des systèmes de fichiers composés de répertoires pointant vers des fichiers, des répertoires ou d'autres instances reconnues par les pilotes / le système d'exploitation.

Pour accéder à tous les éléments, pensez à une chenille qui monte et descend dans la structure. De cette façon, le programme / la fonction / l'ordinateur ne sait rien, il exécute simplement les instructions pour déplacer les données.

Mr. Minty Fresh
la source
Les mots «modèle» et «format» tels qu'utilisés ici n'apparaissent dans aucun compilateur ou manuel de compilation que j'ai jamais vu, et il ne semble pas y avoir de raison d'utiliser les deux mots pour la même chose inexistante. Les variables ont des adresses et / ou des décalages, c'est tout ce que vous devez savoir.
user207421
J'utilise les mots car ce sont des abstractions pour l'arrangement des données, tout comme les nombres, les fichiers, les tableaux et les variables sont des abstractions.
M. Minty Fresh