Comment les pointeurs C sont réellement mappés à la mémoire du tableau

12

C rend les tableaux et les pointeurs indissociables. Vous ne pouvez pas écrire du code efficace sans comprendre leur relation. Il faut du temps pour maîtriser. La récompense est un contrôle immédiat sur la mémoire.

Considérez une configuration de base. Vous déclarez deux tableaux d’entiers de taille dix.

Compilez ceci. Cela échoue. Vous ne pouvez pas attribuer un tableau à un autre à l’aide du signe égal. C ne prend pas en charge la copie en masse via des opérateurs d’affectation. Pour copier a dans b, vous devez parcourir chaque élément.

Vous pouvez compresser cette boucle en une seule ligne avec un opérateur virgule, mais la lisibilité en souffre. L’approche standard consiste à utiliser memcpy à partir de string.h. Il gère efficacement la copie au niveau des octets.

L’échec de b=a révèle une règle fondamentale. En C, les variables tableau ne sont pas des tableaux. Ce sont des pointeurs fixes. a et b sont des identifiants permanents pour l’adresse de départ de leurs blocs mémoire respectifs. a pointe vers a[0]. b pointe vers b[0]. Ces adresses sont immuables. Vous ne pouvez pas rediriger un nom de variable vers un autre emplacement mémoire. C’est pourquoi la mission échoue. Le compilateur traite le nom du tableau comme un pointeur constant.

Puisque le nom est en fait un pointeur, vous pouvez utiliser l’arithmétique du pointeur pour accéder aux données. C’est là que la syntaxe devient puissante.

Ici, p est une variable de pointeur. Vous pouvez attribuer l’adresse du tableau a à p. L’instruction p=a copie l’adresse. Désormais, p contient la même valeur que le nom du tableau a. Le déréférencement de p avec *p récupère le premier élément. La sortie est nulle.

Cette distinction est importante car les variables de pointeur peuvent être modifiées. Vous pouvez faire pointer p vers a[1] ou a[5] en utilisant l’arithmétique. Le nom du tableau lui-même reste bloqué au début. Cette flexibilité permet aux fonctions de parcourir la mémoire sans copier de grandes structures de données. C’est l’épine dorsale des performances de bas niveau en C.

Comprendre ce lien évite des bugs subtils. Cela explique pourquoi certaines fonctions acceptent des pointeurs au lieu de la syntaxe de tableau. Il clarifie la disposition de la mémoire. Ignorez-le et vous serez confronté à des erreurs de segmentation ou à des écrasements involontaires de données. Apprenez-le et vous verrez exactement ce que fait la machine.

La syntaxe peut sembler peu naturelle au début. Le double sens des parenthèses et des astérisques déroute les débutants. Mais une fois que le modèle clique, l’abstraction disparaît. Il vous reste des adresses et des valeurs. Rien de plus.

L’effort mental en vaut-il la peine ? Le code devient plus rapide. L’utilisation de la mémoire diminue. Vous bénéficiez d’un accès direct aux opérations au niveau matériel. La plupart des programmes C s’appuient

Vous pouvez attribuer une variable de tableau directement à un pointeur car, sous le capot, c’est essentiellement la même chose.

p=a; ne fonctionne pas à cause de la magie, mais parce que a se désintègre en un pointeur. Plus précisément, il pointe vers l’adresse mémoire du premier élément du tableau (index 0). Puisque cet élément est un entier, a agit comme un pointeur vers un seul int. Déclarer p comme pointeur entier et le définir égal à a revient simplement à laisser deux variables partager la même adresse. Vous pouvez écrire p=&a[0]; à la place. Le résultat est identique.

Une fois p connecté au début de a, vous gagnez en mobilité. Les noms de tableaux comme a sont des constantes ; ce sont des adresses fixes qui ne peuvent pas changer. Les pointeurs comme p sont différents. Vous pouvez les déplacer en utilisant l’arithmétique du pointeur.

C’est là que C devient intelligent.

Lorsque vous écrivez p++;, le compilateur n’ajoute pas seulement un octet à l’adresse. Il sait que p pointe vers un entier. Il ajoute des octets sizeof(int) à la place, atterrissant exactement sur l’élément suivant. Si p pointait vers une structure de 100 octets de long, p++ avancerait de 100 octets. C gère les mathématiques. Vous déplacez simplement le pointeur.

Copie de tableaux sans index

Vous pouvez copier un tableau dans un autre à l’aide de ces pointeurs. Au lieu d’une boucle standard avec des index :

Vous pouvez utiliser des pointeurs :

C’est verbeux. Vous pouvez le compresser.

Ou réduisez-le en une seule ligne.

La priorité de l’opérateur ici est de faire le gros du travail. * a une priorité plus élevée que ++, mais le post-incrément se produit après la récupération de la valeur. Ainsi *p++ récupère la valeur actuelle, puis déplace le pointeur. *q++ fait la même chose pour la destination. La mission se déroule entre les deux. C’est compact. C’est rapide. Il est également facile de se tromper si vous n’y prêtez pas attention.

La zone dangereuse

Que se passe-t-il si vous poussez p ou q au-delà de la fin du tableau ?

C ne vous arrête pas.

Le compilateur suppose que vous savez ce que vous faites. Il continuera à incrémenter les pointeurs, copiant les données dans la mémoire qui n’appartiennent pas au tableau. Vous pourriez écraser d’autres variables. Vous pourriez corrompre la pile. Vous pourriez planter le programme plus tard, dans un endroit sans rapport avec le bug.

C’est pourquoi la vérification des limites est manuelle en C. Le langage vous fait confiance. Lorsque vous sortez des sentiers battus, vous en payez le prix.

Passer des tableaux aux fonctions

Vous pouvez transmettre des tableaux aux fonctions de deux manières. Ils ont une apparence différente mais se comportent de la même manière.

Considérons une fonction qui imprime un tableau :

Ou la version pointeur :

Le paramètre nia (numéro dans le tableau) est obligatoire. Les tableaux en C n’emportent pas leur taille avec eux. La fonction doit savoir quand s’arrêter.

Dans les deux cas, vous passez un pointeur vers le début du tableau, et non