4.9. Utilisation des pointeurs avec les tableaux
Les tableaux sont étroitement liés aux pointeurs parce que, de manière interne,
l'accès aux éléments des tableaux se fait par manipulation de leur adresse de base, de la taille
des éléments et de leurs indices. En fait, l'adresse du n-ième élément d'un tableau est calculée
avec la formule :
Adresse_n = Adresse_Base + n*taille(élément)
où
taille(élément) représente la taille de chaque élément du tableau et
Adresse_Base l'adresse de base du tableau. Cette adresse de base est l'adresse
du début du tableau, c'est donc à la fois l'adresse du tableau et l'adresse de son premier élément.
Ce lien apparaît au niveau du langage dans les conversions implicites de tableaux
en pointeurs, et dans le passage des tableaux en paramètre des fonctions.
4.9.1. Conversions des tableaux en pointeurs
Afin de pouvoir utiliser l'arithmétique des pointeurs pour manipuler
les éléments des tableaux, le C++ effectue les conversions implicites suivantes lorsque nécessaire :
Cela permet de considérer les expressions suivantes comme équivalentes :
identificateur[n]
et :
*(identificateur + n)
si
identificateur est soit un identificateur de tableau, soit celui d'un pointeur.
Exemple 4-11. Accès aux éléments d'un tableau par pointeurs
int tableau[100];
int *pi=tableau;
tableau[3]=5; /* Le 4ème élément est initialisé à 5 */
*(tableau+2)=4; /* Le 3ème élément est initialisé à 4 */
pi[5]=1; /* Le 5ème élément est initialisé à 1 */
Note : Le langage C++ impose que l'adresse suivant le dernier élément
d'un tableau doit toujours être valide. Cela ne signifie absolument pas que la zone mémoire référencée
par cette adresse est valide, bien au contraire, mais plutôt que cette adresse est valide. Il est donc
garantit que cette adresse ne sera pas le pointeur NULL par exemple, ni toute autre valeur spéciale
qu'un pointeur ne peut pas stocker. Il sera donc possible de faire des calculs d'arithmétique des pointeurs
avec cette adresse, même si elle ne devra jamais être déréférencée, sous peine de voir le programme planter.
On prendra garde à certaines subtilités. Les conversions
implicites sont une facilité introduite par le compilateur, mais en réalité, les tableaux ne sont
pas des pointeurs, ce sont des variables comme les autres, à ceci près : leur type est convertible
en pointeur sur le type de leurs éléments. Il en résulte parfois quelques ambiguïtés lorsqu'on manipule
les adresses des tableaux. En particulier, on a l'égalité suivante :
&tableau == tableau
en raison du fait que l'adresse du tableau est la même que celle de son premier élément. Il faut bien
comprendre que dans cette expression, une conversion a lieu. Cette égalité n'est donc pas exacte
en théorie. En effet, si c'était le cas, on pourrait écrire :
*&tableau == tableau
puisque les opérateurs * et & sont conjugués, d'où :
tableau == *&tableau = *(&tableau) == *(tableau) == t[0]
ce qui est faux (le type du premier élément n'est en général pas convertible en type pointeur.).
4.9.2. Paramètres de fonction de type tableau
La conséquence la plus importante de la conversion tableau vers pointeur
se trouve dans le passage par variable des tableaux dans une fonction. Lors du passage d'un tableau
en paramètre d'une fonction, la conversion implicite a lieu, les tableaux sont donc toujours passés
par variable, jamais par valeur. Il est donc faux d'utiliser des pointeurs pour les passer en paramètre,
car le paramètre aurait le type pointeur de tableau. On ne modifierait pas le tableau, mais bel et bien
le pointeur du tableau. Le programme aurait donc de fortes chances de planter.
Par ailleurs, certaines caractéristiques des tableaux peuvent être
utilisées pour les passer en paramètre dans les fonctions.
Il est autorisé de ne pas spécifier la taille de la dernière dimension
des paramètres de type tableau dans les déclarations et les définitions de fonctions. En effet, la borne
supérieure des tableaux n'a pas besoin d'être précisée pour manipuler leurs éléments (on peut malgré
tout la donner si cela semble nécessaire).
Cependant, pour les dimensions deux et suivantes, les tailles des premières
dimensions restent nécessaires. Si elles n'étaient pas données explicitement, le compilateur ne pourrait
pas connaître le rapport des dimensions. Par exemple, la syntaxe :
int tableau[][];
utilisée pour référencer un tableau de 12 entiers ne permettrait pas de faire la différence entre
les tableaux de deux lignes et de six colonnes et les tableaux de trois lignes et de quatre colonnes
(et leurs transposés respectifs). Une référence telle que :
tableau[1][3]
ne représenterait rien. Selon le type de tableau, l'élément référencé serait le quatrième élément
de la deuxième ligne (de six éléments), soit le dixième élément, ou bien le quatrième élément
de la deuxième ligne (de quatre éléments), soit le huitième élément du tableau. En précisant tous
les indices sauf un, il est possible de connaître la taille du tableau pour cet indice à partir
de la taille globale du tableau, en la divisant par les tailles sur les autres dimensions (2 = 12/6
ou 3 = 12/4 par exemple).
Le programme d'exemple suivant illustre le passage des tableaux
en paramètre :
Exemple 4-12. Passage de tableau en paramètre
int tab[10][20];
void test(int t[][20])
{
/* Utilisation de t[i][j] ... */
return;
}
int main(void)
{
test(tab); /* Passage du tableau en paramètre. */
return 0;
}