[gull] Subtilité de C n° 6 : Tableaux et pointeurs, presque pareils

Marc Mongenet marc at mongenet.ch
Tue Jul 29 20:16:43 CEST 2008


Après quelques mois d'interruption, voici la suite des subtilités
du langage C.

Comme chaque programmeur C sait, la différence entre
tableaux et pointeurs peut sembler quelque peu floue...
  char t[] = "hello";  /* t est de type "tableau de chars". */
  char *p;  /* p est de type "pointeur de char". */
  p = t;  /* parfaitement valable en C */

Voici la règle du K&R qui vient d'être appliquée et qui décrit
précisément les relations entre tableaux et pointeurs :
  « If the type of an expression or subexpression is
  "array-of-T", for some type T, then the value of the
  expression is a pointer to the first object in the array
  and the type of the expression is altered to "pointer to T."
  This conversion does not take place if the expression is
  the operand of the unary & operator, or of ++, --, sizeof,
  or as the left operand of an assignement operator or
  the . operator. »

On remarque que ce n'est pas, comme on peut le lire dans
des explications bâclées, le tableau qui est convertit en
pointeur, mais la valeur de l'expression où apparaît le
tableau.

Qu'on le veuille ou non, la conversion de type tableau en
type pointeur est incontournable en C et l'on est obligé de
l'utiliser à tout bout de champ.

Ainsi, on pourrait croire que si au lieu d'écrire :
  p = t;
on écrivait :
  p = &t[0];
alors on n'utiliserait plus la conversion de tableau en pointeur.
Et bien si !
En effet, l'opérateur postfixé binaire [], qui est appliqué ici à
t et 0, ne fait pas partie des exceptions données ci-dessus
par le K&R (&, ++, --, sizeof, gauche de = et .)...
Du point de vue du compilateur, p = &t[0] se parse ainsi :
((p) = (&((t)[(0)]))). On voit que "t" est une sous-expression,
et son type tableau est altéré en type pointeur avant que
l'opérateur [] ne soit appliqué. L'opérateur & n'est lui appliqué
qu'après, sur le résultat de t[0], qui est de type char.

Conclusion : l'opérateur [] s'applique _toujours_ à un pointeur
et un entier. En plus, par définition, E1[E2] est équivalent à
*((E1)+(E2)). On a donc l'équivalence suivante :
  p = &t[0];
  p = &*((t)+(0));
Et comme l'addition est commutative, on peut aussi écrire :
  p = &*((0)+(t));
ce qui est équivalent à :
  p = &0[t]; /* parfaitement valable en C */
Certes, ces dernières lignes n'ont pas d'utilité hors des
explications académiques et des concours d'obsfucated C.

Voici donc un exemple plus commun, hello world :
  #include <stdio.h>
  int main() {
    printf("hello, world\n");
  }
Le prototype de printf est :
  int printf(const char*, ...);
On voit que printf attend un pointeur de char (constant,
mais on verra ça un autre jour), et qu'on lui donne la
chaîne littérale "hello, world\n", dont le type est "tableau
de 14 chars". Encore une fois, ce simple hello world
repose sur la conversion de type tableau en type pointeur.
Je rappelle (voir la subtilité n° 5) que C ne permet pas de
passer des tableaux en paramètre de fonction.

Un petit exercice pour la route :
#include<stdio.h>
int main() {
  int i;
  for (i = 0; putchar(i["hello world\n"]); ++i);
}
Que fait ce programme, au juste ?

-- 
Marc Mongenet
Creator of the Web 2 Markup Language
http://w2ml.com



More information about the gull mailing list