Types avancés, Typedef, memset
Un ordinateur est tout aussi capable d’effectuer des calculs que de garder en mémoire le résultat de calculs précédents. La mémoire aux yeux d’un programme C est un vaste suite de “zeros” et “uns” : les bits, groupés en multiplets ou encore bytes de 8 bits appellés octets.
Le C accède à la mémoire de l’ordinateur via un adressage par octets, c’est à dire pas échelons de 8 bits. Cette taille correspond à la taille des registres du processeur : il est possible de placer en mémoire le calcul du processeur et inversement lui fournir des opérandes à partir de la mémoire de manière triviale et transpartente de cette façon.
Cette mémoire est généralement abstraite aux yeux du C et il n’est pas possible de cibler précisément un registre en particulier, ou un cache de registre, de processeur, ou la mémoire vive elle même. En pratique on considère que le C ne travaille qu’avec la mémoire vive. Ce C idéal qui ignore toutes les optimisations concrètes du code et de la mémoire est parfois appellé « machine virtuelle du C », car lorsqu’un constructeur de processeur écrit un compilateur C, il doit s’assurer que le programme s’exécute sur cette machine virtuelle, avant de le convertir en un programme plus concret et plus optimisé à son architecture. Cette machine virtuelle empêche parfois certaines optimisations et il est généralement préférable d’écrire des programmes peu chargés en mots clés, et d’éviter des manipulations de pointeurs de bas niveau qui ne sont pas strictement nécessaires.
Cette machine virtuelle considère que l’ordinateur ne possède qu’une seule bande de mémoire, adressable de zero jusque sa taille maximale. Si cette représentation de la mémoire était correcte dans les années 70, aujourd’hui la mémoire est souvent fragmentée sur plusieurs « barettes » de mémoire vive, mais tous les fabricants de processeur s’assurent que cette abstraction soit valide.
Cette mémoire est accessible au processeur, c’est à dire que n’importe quel emplacement mémoire peut être utilisé comme opérande d’un calcul arithmétique.
L’unité de travail d’un processeur est l’octet, c’est à dire que
lorsqu’il effectue des calculs,
il va chercher les valeurs en mémoire par tranches de 8 bits, et les
charger dans son registre, soit un mot simple WORD
de 1 octet de 8 bits, un
DWORD
(double word) de 2 octets de 16 bits, soit un QWORD
de 4 octets de 32
bits. Selon qu’on ait un processeur 16, 32 ou 64 bits, cela détermine la taille
maximale d’octets que celui-ci peut charger dans ses registres et sur
lesquels il peut travailler et la taille des nombres qu’il peut manipuler et
l’espace mémoire qu’il peut adresser.
Lorsque le processeur effectue une opération arithmétique, il charge les deux opérandes dans deux registres du processeur, puis effectue l’opération voulue (somme, multiplication, division…), et enfin place le résultat dans le premier registre. En langage machine cela peut se traduire
LOAD A 42 // met 42 dans le registre A
LOAD B 42 // met 42 dans le registre B
SUM // le processeur calcule la somme puis la place dans le registre A
READ A // 84
Un processeur pourrait mettre le résultat dans le registre C, ou écraser la valeur d’un registre existant : c’est ce qui se fait le plus souvent. Ici en faisant SUM le résultat est place dans le registre A.
Que se passe-t-il si le registre n’est pas assez grand pour contenir les nombres ? Dans ce cas une erreur d’opérande (integer overflow) survient où les bits qui « n’entrent pas dans le moule » sont tronqués, avec pour conséquence un nombre résultant totalement différent du nombre voulu. Pour palier cela les constructeurs d’ordinateur dans les années 1960 à 70 se sont lancés dans une course au bit, avec qui propose des ordinateurs à 6 bits, à 8, 10, 12, 14, 16, 24, jusque 32 bits.
Avec la popularité de l’EBCDIC, codé sur 8 bits, et surtout celle de l’ASCII, codé sur 7 bits, il est apparu que la manière la moins dispendieuse en espace pour stocker du texte est de les mettre dans des mots de 8 bits, soit des octets. Pour être capable de travailler avec des octets, mais également des valeurs plus grande qu’un octet, les processeurs se sont vus intégrer des registes « extensibles » à 16 bits, capables de travailler à la fois avec des mots de 16 bits et 8 bits. Ces registres étendus s’appellent sur une architecture Intel, AX, BX, CX et DX. Une machine 32 bits permet de manipuler des entiers de 4 octets avec les registres EAX, EBX, ECX et EDX. Une machine 64 bits propose des registres RAX, RBX, RCX et RDX.
Il est possible de sous-utiliser un processeur 64 bits, 32 bits ou 16 bits c’est à dire de cibler ses registres EAX, AX et A. Cela permet à l’architecture intel d’être rétrocompatible : un processeur intel est en effet capable d’exécuter un programme compilé il y a plus de 50 ans. En pratique un programme est concu pour s’exécuter au sein d’un système d’exploitation qui peut faire évoluer son API de manière non rétrocompatible et interdire de l’exécuter.