Des pointeurs et des hommes (4)
Hello ! Décidément - vacances obligent - les mises à jour se font sur ce blog à une fréquence bien plus élevée que je ne le pensais ! Aujourd’hui, je vous propose de continuer notre découverte des pointeurs en allant creuser un peu du côté des tableaux et de leur lien avec les pointeurs.
Tableaux et pointeurs
En C, la forme la plus simple de tableau est le tableau statique. Pour le déclarer, on procède de la façon suivante :
Avec cette déclaration, le compilateur réserve de la place en RAM, suffisamment pour héberger 10 entiers.
Comme vous l’a surement expliqué votre prof de C, les cases de notre tableau sont numérotées à partir de zéro. L’accès à chaque case se fait avec une syntaxe simple dans laquelle on utilise le “numéro de case” entre crochets.
Par exemple, pour mettre les valeurs 0,1,2…9 dans notre tableau, on pourra écrire :
En RAM, cela ressemble à ceci :
Chaque case du tableau est un entier tout à fait habituel, codé sur 4 octets, et les cases sont stockées les unes derrière les autres.
On peut d’ailleurs le vérifier très facilement en modifiant un tout petit peu notre programme :
Cependant, si jolitab[i] est un int … Quelle est la nature de jolitab ? Oui, oui, jolitab tout court, sans crochet ? Et bien c’est un pointeur. C’est même un pointeur qui contient l’adresse de la première case du tableau.
Et je vous le prouve :
Car oui, depuis le début, vous manipulez sans le savoir des pointeurs quand vous utilisez un tableau ! Et c’est ce qu’il faut en retenir :
Un tableau, en C, ce n’est que l’adresse de sa première case !
Normalement, à ce stade de l’explication, un truc doit se déverrouiller dans votre tête. Un peu en mode “ouais, ça m’avait toujours eu l’air louche cette histoire de tableau …”. Et vous aviez raison !!!
Mais alors ? On m’a menti ?
Oui, un tout petit peu. Mais n’en voulez pas trop à votre prof et mettez vous à sa place : vous aviez besoin des tableaux, il était trop tôt pour vous faire plonger dans les pointeurs, que faire ?
Quand on transmet un tableau à une fonction, vous pouvez choisir deux syntaxes pour la déclaration de la fonction :
ou
Les deux sont strictement équivalentes !
On vous a probablement dit que le C passait les arguments “par valeur”, mais que les tableaux étaient transmis “par référence”, et que donc on envoyait le tableau lui même, pas une copie. Faites moi plaisir : brulez cette partie de votre cours. C’est juste rendre compliquées des choses qui sont pourtant si simples !!!!!!
Quand vous transmettez un tableau à une fonction, vous transmettez juste l’adresse de sa première case. Et ca marche comme exactement toutes les autres variables : puisque c’est un passage de l’adresse, la fonction peut accéder au tableau.
D’ailleurs … puisqu’on en parle, qu’est-ce que c’est que ces crochets que nous utilisons ? C’est là aussi une écriture de pointeurs !
Ecrire jolitab[i] c’est exactement la même chose que d’écrire *(jolitab+i)
On prend ce qui est à l’adresse de base “jolitab”, décalée de i cases ! Elégant non ?
Regardez, je vous illustre ça de suite :
Houlà. Alors à ce stade là, quelques commentaires sont nécessaires, parce que c’est moins évident que ça n’en a l’air.
Si Jolitab vaut 0x0x7ffc8620eb00, est que l’adresse de Jolitab[3] est 0x7ffc8620eb0c, il y a un souci dans le calcul !
Oui, mais c’est parce qu’on est en train de faire de l’arithmétique sur pointeur ! Et je vais vous en parler de suite …
Arithmétique sur pointeur
Les pointeurs, en C, sont typés. Et cela est important. Admettons que j’ai un pointeur vers un entier, noté P :
Quand je décale P, je veux qu’il pointe vers l’entier suivant non ? Il faut donc que je le décale de 4 Octets.
L’opération P+1 va justement tenir compte de cela en incrémentant bien l’adresse contenue dans P de 4 octets, soit la taille d’un int. Les opérations sur les pointeurs tiennent compte du type desdits pointeurs :
P est passé de 0x7fff158dfd30 à 0x7fff158dfd34 : l’incrément de l’adresse dans P est bien de 4 octet, soit la taille d’un int.
Graphiquement :
On peut vérifier que cela marche aussi pour d’autre types comme le double, par exemple :
Cette fois-ci, le pointeur étant de type double*, nous faisons des sauts de 8 Octets ! ( la taille d’un double … )
Hola, je vous vois venir vous ! vous allez me dire : “Et si le pointeur est non typé ? Si c’est un void* ?”
Et bien c’est très simple : si le pointeur est non typé, l’incrémentation se fait octet par octet.
Vous savez maintenant comment manipuler les pointeurs. Tout cela n’est pas si compliqué, mais c’est très habilement conçu à mon humble avis. En C, l’articulation entre tableaux et adresses est particulièrement naturelle. (Avis 100% biaisé d’un amoureux du C : j’assume !)
Retour au tableau
Cette notation utilisant les crochets, tab[i], ou son équivalent *(tab+i), ne sont au final que des calculs d’adresses.
On peut d’ailleurs souligner deux choses. La première, et non des moindres, est que tout cela explique clairement pourquoi les cases d’un tableau sont indicées de 0 à N-1, et non pas de 1 à N comme dans d’autres langages. La case zéro, c’est l’adresse du début du tableau … décalée de zéro justement !
Une autre chose, plus méconnue celle-ci, est qu’on peut inverser la notation. Si tab[i] c’est *(tab+i) … alors c’est aussi *(i+tab) on est d’accord ? Oui, vos pupilles écarquillées et votre tension artérielle soudaine ne trompent pas, vous avez compris où je veux en venir.
Il est tout à fait légal, en C, d’utiliser i[tab] au lieu de tab[i]. Ca marche pareil ! Et le compilo l’accepte !!!!!!
Vous doutez ? Compilez donc ceci :
Gcc ne crache même pas le plus petit warning : ca compile et fonctionne parfaitement !
Par contre, ce n’est absolument pas lisible, et aucun programmeur sérieux ne fait ce genre de chose : je vous donne juste cette anecdote pour illustrer mon propos et vous montrer la logique de la chose. NE FAITES JAMAIS CA DANS LA VRAIE VIE. Vraiment. Sinon je bute un bébé chat et ce sera votre faute !
Jouer avec les pointeurs …
Ca, par contre, je vous autorise à le faire : utiliser astucieusement les pointeurs et les types.
Prenons un entier. C’est une donnée sur 4 Octets, vous êtes d’accord ?
Prenons maintenant un tableau de 4 chars … c’est aussi une donnée sur 4 octets, vous êtes d’accord ?
Puisqu’un tableau n’est qu’une adresse, on va utiliser un peu nos connaissances. Je vous laisse lire le code suivant :
Dans ce programme, A contient une valeur. Si nous passons par le pointeur tab, qui contient l’adresse de A, nous voyons ces 4 octets comme un tableau de 4 cases d’un octet chacune.
C’est toujours la même donnée, mais interprétée autrement !
D’après google, 115200 s’écrit en 4 octets dont les valeurs respectives sont : 00 01 C2 00. C’est bien le contenu des cases de tab.
Note : Il faut se rappeler que les PC sont des architectures little-endian ! C’est normal que les octets soient “à l’envers” :)
Le mot de la fin
Il se fait tard (1h20 du matin à ma montre) alors je vous propose de nous arrêter ici. J’espère que c’est clair, et n’hésitez pas, quand vous lirez ces lignes, à me dire ce que vous avez compris ou pas. Il existe plein d’exercices, ça et là sur le Net, que vous pouvez faire pour mieux maitriser la chose. Je vous y encourage vivement, car c’est en programmant que vous maitriserez tout ça !
A bientôt,
Rancune.