Pour vraiment comprendre pourquoi les pointeurs se comportent comme ils le font, vous devez arrêter de penser en termes de variables abstraites et commencer à visualiser le matériel. C’est un modèle simple, mais c’est le fondement de tout, des erreurs de segmentation aux vulnérabilités de sécurité. Si vous êtes flou sur les bases des bits, des octets et des mots, vous devriez probablement faire une pause ici et réviser cela en premier. Le reste de cette explication suppose que vous connaissez la différence entre un octet et un bit.
Votre ordinateur dispose de mémoire ou de RAM (mémoire vive). Vous pourriez en avoir 16, 32 ou 64 Go installés. Il ne s’agit pas seulement de stockage ; c’est l’espace de travail. Il contient le code que votre processeur exécute actuellement et les données que ces programmes manipulent à cette seconde précise. Considérez la RAM comme un tableau massif et linéaire d’octets.
Chaque octet de ce tableau a une adresse. Le premier octet est l’adresse 0. Le suivant est 1. Puis 2, 3, et ainsi de suite. Ces adresses fonctionnent exactement comme les index dans un tableau de programmation standard. Étant donné que le processeur peut accéder instantanément à n’importe quelle adresse, on parle de « mémoire vive ». Il n’est pas nécessaire de le lire de manière séquentielle. Il peut récupérer tout ce dont il a besoin, quand il en a besoin. Lorsque le compilateur doit stocker un type de données plus volumineux, il récupère simplement un bloc d’octets contigus. Un nombre à virgule flottante standard, par exemple, occupe généralement 4 octets contigus.
Regardons une déclaration globale en C :
Pour vous, il s’agit d’une variable nommée « f » qui contient un flottant. Pour l’ordinateur, il s’agit d’une commande permettant de réserver 4 octets spécifiques dans la matrice mémoire. Supposons que le compilateur attribue ces octets à l’emplacement mémoire 248 440.
Quand tu écris :
Le compilateur ne pense pas à “mettre à jour la variable f”. Il pense “charger la valeur 3,14 dans l’adresse mémoire 248 440”. L’abstraction est mince. Sous le capot, tout est question d’adresses et de valeurs qui y sont associées.
Cette nature mécanique de la mémoire entraîne des effets secondaires désagréables. Considérez cet extrait de code C :
Vous pouvez vous attendre à ce que s et t contiennent 0:0, 1:1, 2:2, 3:3. Et « u » devrait rester 0. Au lieu de cela, le résultat ressemble à ceci :
Attendez. Qu’est-il arrivé à « t[0] » ? Et pourquoi « u » a-t-il maintenant 5 ans ?
Le problème est un débordement de tampon classique. Regardez attentivement la condition de boucle : i<=4. Le tableau « t » est déclaré comme « t[4] », ce qui signifie qu'il a les indices 0, 1, 2 et 3. Lorsque « i » atteint 4, le code écrit dans « t[4] ». Cet index n'existe pas dans l'espace alloué pour « t ». Il écrit un élément après la fin du tableau.
Parce que la mémoire est contiguë, l'ordinateur place « s », « t » et « u » les uns à côté des autres dans le tas ou la pile.
Lorsque vous écrivez au-delà de la fin d'un tableau, vous ne plantez pas immédiatement. Vous écrasez tout ce qui se trouve dans l’emplacement mémoire juste à côté.
Dans ce cas spécifique, l'écriture dans « t[4] » écrase l'emplacement mémoire contenant « u » (qui était à l'origine 0). Mais pourquoi es-tu

Écrivez dans s[4]. Cet index n'existe pas. Mais l'ordinateur s'en fiche. Il ne vérifie pas si vous marchez sur vos propres pieds. Il calcule simplement l'adresse. Et cette adresse arrive sur t[0].
Vous pensez que vous écrivez à « s ». En fait, vous écrivez à « t ». Le système exécute la commande sans clignoter. La logique est erronée. Le programme corrompt les données. C'est silencieux jusqu'à ce que ce ne soit plus le cas.
Maintenant, essayez quelque chose de pire.
s[1000000] = 5;
Vous écrivez dans une mémoire que votre programme ne possède pas. Les conséquences dépendent entièrement du système d'exploitation que vous utilisez. Sur les systèmes protégés comme UNIX, Windows NT ou Windows 98, le système d'exploitation détecte immédiatement cette violation. Il termine le programme. Arrêt dur. Il protège le reste du système de votre erreur.
Les systèmes plus anciens comme Windows 3.1 ou Mac OS classique n'ont pas ce luxe. Ils ne savent pas ce que vous faites. Vous finissez par écraser du code ou des variables dans une autre application. Le résultat ? Un problème. Un accident. Ou pire, une panne à l’échelle du système qui semble aléatoire mais qui est en réalité le résultat direct d’un accès mémoire non contrôlé.
En mémoire, des variables telles que « i », « s », « t » et « u » sont placées les unes à côté des autres à des adresses spécifiques. Ce sont des voisins. Si vous écrivez au-delà des limites de l’un, vous débordez sur le suivant. L'ordinateur fait exactement ce que vous avez dit. Peu importe que vous ayez de bonnes intentions.
Le danger d'un accès non contrôlé aux baies
C et C++ n'effectuent pas de vérification de plage. Ils supposent que vous savez ce que vous faites. Cela signifie que vous devez prêter une attention particulière aux plages de tableaux. Si vous lisez ou écrivez en dehors des limites, le comportement est défectueux. Imprévisible. Dangereux.
Ce manque de sécurité est la raison pour laquelle les erreurs de pointeur non initialisé sont si courantes en C. Considérez ce code :
Le compilateur imprime la valeur dans p et l'adresse de i. Initialement, p contient des déchets ou zéro. L'adresse de « i » est généralement un grand nombre. Par exemple, vous pourriez voir :
Après p = &i, p contient l'adresse de i. Simple.
Maintenant, regardez ceci :
Ceci imprime la valeur vers laquelle « p » pointe. Mais p n'est pas initialisé. Il pointe vers l'adresse 0 ou un bit aléatoire de mémoire. Le résultat est presque toujours une erreur de segmentation. Une erreur d'exécution. Vous essayez d'accéder à une mémoire qui ne vous appartient pas.
Les pointeurs sous un nouveau jour
Une fois que vous comprenez ces risques, les indicateurs changent. Ce ne sont pas seulement des variables. Ce sont des adresses directes vers des emplacements mémoire.
Suivez ce programme :
Voici ce qui se passe :
Comment les pointeurs stockent les adresses mémoire
Lorsque vous déclarez une variable entière comme i, elle occupe 4 octets d'espace dans la RAM. Un pointeur, appelons-le p, occupe également 4 octets. Cette taille est vraie pour la grande majorité des systèmes actuellement en circulation, où les adresses mémoire ont une longueur de 32 bits. L'industrie évolue lentement vers l'adressage 64 bits, mais pour l'instant, l'empreinte de 4 octets est la norme.
Considérez i comme une maison. Il a une adresse postale spécifique, disons 248 440. Le pointeur p n'est pas la maison elle-même. C'est un morceau de papier sur lequel est écrite cette adresse. Lorsque vous attribuez p = &i, vous écrivez 248 440 sur ce papier.
Cette distinction est essentielle pour comprendre comment fonctionnent les pointeurs en C. Le pointeur ne contient pas la valeur de i. Il contient l'emplacement où i habite. Si vous déréférencez p (en utilisant p *), vous allez à cette adresse et lisez la valeur. En ce sens, p et i * sont fonctionnellement identiques. Ils pointent vers les mêmes données.
Affichage de l'adresse dans le code
Vous pouvez vérifier ce comportement avec une simple instruction d'impression.
printf("%d", p);
Cette commande n'imprime pas la valeur de i. Il imprime le numéro écrit sur le papier. La sortie est l'adresse mémoire réelle de i.
Pourquoi est-ce important ? Parce qu'il vous permet de transmettre des emplacements plutôt que des copies de données. Vous pouvez manipuler la mémoire directement. Vous pouvez relier des structures entre elles. Vous pouvez gérer efficacement la mémoire tas. Le pointeur est une référence. La variable est le contenu. Confondre les deux conduit à des bugs. Comprendre la différence mène au contrôle.
Certains développeurs considèrent les pointeurs comme de la magie. Ce n’est pas le cas. Ce ne sont que des chiffres. Les adresses mémoire sont des entiers. Traitez-les comme tels.

















