• Aucun résultat trouvé

Il y a un moment dans l'etude du langage C, ou on a l'impression que les tableaux et les pointeurs sont plus ou moins interchangeables, en un mot que c'est pratiquement la m^eme chose. il faut donc ^etre tres clair: un tableau et un pointeur

ce n'est pas

la m^eme chose. Quand on declare, ailleurs qu'en parametre formel de fonction, int t[10]; on declare un tableau, et le compilateur reserve une zone de 10 entiers consecutifs. Quand on declare int *p, il s'agit toujours d'un pointeur, et le compilateur reserve simplement un element de memoire pouvant contenir un pointeur.

Les caracteristiques du langage C qui creent la confusion dans l'esprit des utilisateurs, sont les trois regles suivantes:

1. tout identi cateur de type tableau dexapparaissant dans une expression est converti

en une valeur constante de type pointeur vers x, et ayant comme valeur l'adresse du premier element du tableau;

2. un parametre formel de fonction, de type tableau de x est considere comme etant

de type pointeur vers x;

3. la semantique de l'operateur d'indexation est la suivante: T[i] est equivalent a *(T + i).

4.13.1 Commentaires

Bien noter les points suivants:

 Le point important a comprendre dans la regle 2 est que tableau de x est la m^eme

chose que pointeur vers x

uniquement dans le cas de parametre formel de

fonction

. Doncvoid fonc (int t[]) { ... } est equivalent a

void fonc (int * t) { ... }. Les types des objets declares de type tableau ou de type pointeur sont di erents dans tous les autres contextes, que ce soit declaration de variables globales ou locales a une fonction.

 Di erence entre regle 1 et regle 2: une declaration int t[10] qui n'est pas un

parametre formel, declare un tableau de 10 entiers. Ce sont les utilisations ulterieures de t qui subissent une conversion en type pointeur vers entier. Par contre, a la declaration d'un parametre formel int t[], c'est la declaration elle-m^eme qui est transformee en int *t.

4.13.2 Cas particulier des cha^nes litterales

Les cha^nes litterales viennent ajouter a la confusion, car on peut declarer

char t[] = "Hello";etchar *p = "Hello";. Dans le premier cas, le compilateur alloue un tableau de 6 caracteres qu'il initialise avec les caracteres H, e, l, l, o et \0. Toute occurrence de t dans une expression sera convertie en type pointeur vers char. Dans le second cas, le compilateur alloue un tableau de 6 caracteres qu'il initialise de la m^eme maniere que dans le premier cas, mais de surcro^t, il alloue une variable de type pointeur vers charqu'il initialise avec l'adresse du premier caractere de la cha^ne.

char t[] = "Hello"; t : H e l l o \0

char *p = "Hello"; p : H e l l o \0

Ceci est un cas particulier des tableaux de caracteres qui ne se reproduit pas avec les autres types. On peut declarer un tableau d'entiers par int t[] = {1, 2, 3, 4, 5};, mais on ne peut pas declarer un pointeur vers un tableau d'entiers par:

int *p = {1, 2, 3, 4, 5};.

4.14 Recreation

En illustration sur les bizarreries des tableaux dans le langage C, voici la contribution de David Korn (le createur du korn shell) a la competition du code C le plus obscur (ioccc) de 1987 :

main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}

Non, ce programme n'imprime pas have fun with unix ou quelque chose de ce genre! Le lecteur est invite a essayer d'elucider ce programme (oui, il imprime quelque chose, mais quoi?) la solution est donnee a la page suivante.

Voici les cles de la comprehension:

1. Tout compilateur C possede un certain nombre de constantes prede nies dependant de l'architecture de la machine sur laquelle il s'execute. En particulier, tout compi- lateur pour machine unix prede nit la constante unixavec comme valeur 1. Donc le programme est equivalent a:

main() { printf(&1["\021%six\012\0"],(1)["have"]+"fun"-0x60);}

2. Si on se souvient qu'on a vu en 4.2 que pour un tableau t, t[i] est equivalent a i[t], on voit que1["\021%six\012\0"]est equivalent a"\021%six\012\0"[1]et (1)["have"]a"have"[1]. Donc&1["\021%six\012\0"]est l'adresse du caractere %dans la cha^ne"\021%six\012\0"et"have"[1]est le caractere 'a'. On peut donc reecrire le programme:

main() { printf("%six\012\0", 'a' + "fun" -0x60);}

3. La n d'une cha^ne est signalee par un caractere null (\0) et le compilateur en met un a la n de chaque cha^ne litterale. Celui qui est ici est donc inutile. D'autre part, il existe une notation plus parlante pour le caractere de code\012(c'est a dire new line), il s'agit de la notation\n. Le programme peut donc se reecrire :

main() { printf("%six\n", 'a' + "fun" -0x60);}

4. Le caractere 'a' a pour code ascii 0x61, donc 'a' -0x60 est egal a 1. Reecrivons le programme:

main() { printf("%six\n","fun" + 1); }

5. "fun" + 1 est l'adresse du caractere u dans la cha^ne "fun", le programme devient donc:

main() { printf("%six\n","un"); } il imprime donc unix.

Les entrees-sorties

A ce moment-ci de l'etude du langage, le lecteur eprouve sans doute le besoin de disposer de moyens d'entrees-sorties plus puissants que les quelques possibilites deprintf etscanfque nous avons presentees. Nous allons donc consacrer un chapitre entier a cette question, en presentant les primitives les plus utiles de la bibliotheque standard. Toutes les primitives ne sont pas presentees, mais celles qui le sont, sont presentees de maniere exhaustive et conforme a la normeansi.

5.1 Pointeur invalide

Quand on programme des listes cha^nees, on a besoin de disposer d'une valeur de pointeur invalide pour indiquer la n de la liste. Dans le chier d'include stddef.h se trouve la de nition d'une telle valeur qui porte le nom de NULL. Un certain nombre de fonctions de la bibliotheque standard qui doivent rendre un pointeur, utilisent la valeur NULLcomme indicateur d'erreur.

Documents relatifs