CitationSauf quand elle est l'opérande de l'opérateur sizeof ou de l'opérateur unaire &, ou est une chaine de caractères littérale utilisée pour initialiser un tableau, une expression de type "tableau de type" est convertie en une expression de type "pointeur de type" qui pointe sur l'élément initial de l'objet tableau et n'est pas une lvalue.
Comme revu dans l'introduction, lorsqu'une expression de type tableau est l'opérande de l'opérateur d'affectation (
p=tab
), d'addition (
tab+2
), d'appel de fonction (
printf("hello, world\n")
), et de beaucoup d'autres opérateurs, alors elle se comporte comme un pointeur constant sur l'élément initial du tableau. En fait, il n'existe que trois cas où une expression de type tableau se comporte clairement comme un tableau. Ils sont soulignés dans l'extrait de la norme ci-dessus, et illustrés ci-dessous.
sizeof tab
Code : C 1
2
3
4
5
6
7
8
9
10
11
12 | #include <stdio.h>
int main(void)
{
char tab[3];
char *p = tab;
printf("sizeof tab vaut %d.\n", (int)sizeof tab);
printf("sizeof p vaut %d.\n", (int)sizeof p);
return 0;
}
|
Ce code affiche le texte suivant sur mon système:
Code : Console | sizeof tab vaut 3.
sizeof p vaut 8. |
Il se peut que la taille de
p
soit différente de 8 sur ton système (4 est une valeur courante).
Ce qu'il est important de constater, c'est que la taille du tableau
tab
est différente de celle du pointeur
p
. En effet, lorsque l'opérande de
sizeof
est de type tableau, alors le résultat est la taille du tableau, et pas la taille d'un pointeur sur un élément du tableau.
J'ai écrit (int)sizeof tab
et (int)sizeof p
avec conversion en int
, afin de correspondre au format "%d"
de printf
. Sinon il y a un risque de bogue, car sizeof
crée un entier de type size_t
. Si le compilateur supporte C99, alors on peut écrire printf("sizeof p vaut %zu.\n", sizeof p);
sans conversion.
On peut se demander pourquoi, une fonction size_t my_strlen(char s[]){return sizeof s;}
retourne la taille d'un pointeur de char
, et pas la taille du tableau contenant la chaine de caractères s
. La réponse est très simple: parce que dans ce cas, s
est un pointeur de char
. C'est une particularité des déclarations de paramètres de fonction sur laquelle je reviendrai plus tard...
&tab
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13 | int main(void)
{
int tab[3];
int *p;
int **pp;
int (*pt)[3];
p = &tab;
pp = &tab;
pt = &tab;
return 0;
}
|
Dans le code ci-dessus, deux des trois affectations de pointeur provoquent un avertissement de la part du compilateur:
Code : Console | $ gcc ex3.c
ex3.c: In function ‘main’:
ex3.c:8:7: attention : assignment from incompatible pointer type
ex3.c:9:8: attention : assignment from incompatible pointer type
$ |
Seule l'affectation
pt=&tab
ne provoque aucun avertissement. En effet, le type de
pt
est le même que celui de l'expression
&tab
: «pointeur de tableau de 3
int
», ce qui s'écrit
int(*)[3]
en C.
On remarque en particulier que le type de l'expression
&tab
n'est pas
int**
. Ce serait le cas si l'expression
tab
était convertie en pointeur de
int
(soit
int*
) avant l'opération unaire
&
. Mais ce n'est pas le cas; le type de
&tab
est donc un pointeur sur un tableau, et pas un pointeur sur un pointeur.
Attention à la paire de parenthèses en notant le type int(*)[3]
. Si on écrit int*[3]
, cela signifie «tableau de 3 pointeurs de int
», car l'opérateur *
a une précédence inférieure à l'opérateur []
.
char tab[] = "chaine"
Code : C | int main(void)
{
char *p = "hello";
char t1[] = "world";
char t2[] = p; /* FAUX */
char t3[] = (char*)"foo"; /* FAUX */
return 0;
}
|
Pourquoi les initialisations de
t2
et
t3
ne compilent pas, alors que celle de
t1
est parfaitement valable?
Car un tableau ne peut pas être initialisé avec une valeur scalaire, or un pointeur est une valeur scalaire, et les initialisateurs de
t2
et
t3
sont des pointeurs.
En revanche, la chaine
"world"
qui sert à initialiser
t1
n'est pas convertie en pointeur. La chaine
"world"
est de type tableau de 6
char
, et l'initialisation fait de
t1
un tableau de 6
char
. C'est équivalent à
char t1[]={'w','o','r','l','d',0};
Enfin pour initialiser
p
, le tableau
"hello"
est classiquement converti en un pointeur sur son élément initial.
Pour être propre, il faudrait écrire const char *p = "hello";
mais ça compliquerait inutilement les explications.