English

Manuel de référence du langage C

1. Introduction

Le présent manuel décrit le langage C tel qu'il est défini dans le projet soumis à l'ANSI le 31 octobre 1988, en vue d'approbation sous le nom Norme nationale américaine pour les systèmes d'information - Langage de programmation C (American National Standard for Information Systems - Programming Language C ), document numéro X3.159-1989. Ce manuel ne constitue qu'une interprétation du projet de norme, et non la norme proprement dite, bien que nous nous soyons efforcés d'en faire un guide fiable du langage.

La plus grande partie de ce manuel suit les grandes lignes du plan de l'avant-projet de norme, qui se fonde lui-même sur celui de la première édition de ce livre, bien que certains détails de l'organisation soient différents. La grammaire du langage proprement dit que nous donnons ici est équivalente à celle de l'avant-projet actuel, mis à part que nous avons modifié les noms de certaines productions, et que nous n'avons pas formalisé les définitions des lexèmes (tokens), ni celles du préprocesseur.

Tout au long de ce manuel, les commentaires sont mis en retrait et sont de plus petite taille que le texte, comme le présent paragraphe. La plupart du temps, ces commentaires soulignent les différences entre le C de la norme ANSI d'une part, et le langage défini par la prernière édition de ce livre, des perfectionnernents introduits par la suite dans divers compilateurs d'autre part.

 

2. Les conventions lexicales

Un programme se compose d'une ou plusieurs unités de traduction stockées dans des fichiers. Sa traduction s'effectue en différentes phases, décrites au §12. Les premières phases réalisent des transformations lexicales de bas niveau, exécutent les directives que donnent les lignes commençant par le caractère #, et s'occupent de définir et de développer les macros. A la fin du prétraitement du §12, le programme se réduit à une séquence de lexèmes.

2.1 Les lexèmes

Il existe six sortes de lexèmes : les identificateurs, les mots-clés, les constantes, les constantes de type chaîne, les opérateurs, et les autres séparateurs. Les espaces, les tabulations horizontales et verticales, les caractères de fin de ligne, les caractères de saut de page, ainsi que les commentaires tels que nous les décrivons ci-dessous, s'appellent collectivement les "caractères d'espacement" et sont ignorés, sauf lorsqu'ils séparent des lexèmes. En effet, les caractères d'espacement sont indispensables pour séparer des identificateurs, des mots-clés et des constantes qui seraient adjacents sans eux.

Si le flot d'entrée a été divisé en lexèmes jusqu'à un caractère donné, le lexème suivant est défini comme la plus longue chaîne de caractères pouvant constituer un lexème.

2.2 Les Commentaires

Les caractères /* marquent le début d'un commentaire, qui se termine par les caractères */. Les commentaires ne s'imbriquent pas, et ne figurent pas à l'intérieur de constantes de type chaîne ou caractère.

2.3 Les identificateurs

Un identificateur est une séquence de lettres et de chiffres. Le premier caractère doit être une lettre ; le caractère de soulignement _ compte comme une lettre. Les lettres majuscules et minuscules sont différenciées. Les identificateurs peuvent être de longueur quelconque, et les identificateurs internes peuvent comporter au moins 31 caractères significatifs ; sur certaines implémentations, le nombre de caractères significatifs peut être plus grand. Les identificateurs internes sont les noms de macros du préprocesseur et tous les autres noms n'ayant pas de lien externe (§l1.2). Les identificateurs ayant un lien externe obéissent à des contraintes plus fortes : les implémentations ont le droit de réduire le nombre de caractères significatifs à six, et de ne pas distinguer les majuscules des minuscules.

2.4 Les mots-clés

Les identificateurs suivants sont réservés pour servir de mots-clés ; il ne peuvent servir à rien d'autre ;

        auto      double    int       struct 
        break     else      long      switch 
        case      enum      register  typedef 
        char      extern    return    union 
        const     float     short     unsigned 
        continue  for       signed    void 
        default   goto      sizeof    volatile 
        do        if        static    while 

Certaines implémentations réservent aussi les mots fortran et asm.

Les mots-clés const, signed et volatile sont des nouveautés de la norme ANSI ; enum et void ne figuraient pas dans la première édition, mais ils sont employés couramment ; entry, qui était réservé mais n'a jamais été utilisé, ne l'est plus.

 

2.5 Les constantes

Il existe différentes sortes de constantes, chacune ayant un type particulier ; les types de base sont abordés au §4.2.

constante :

 

constante-entière
constante caractère
constante-flottante
constante-énumérée

 

2.5.1 Les constantes entières

On considère qu'une constante entière, composée d'une séquence de chiffres, est écrite en octal si elle commence par un 0 (le chiffre zéro), sinon en décimal. Les constantes octales ne peuvent pas contenir les chiffres 8 et 9. Une séquence de chiffres précédée de 0x ou 0x (avec le chiffre zéro) est considérée comme un entier hexadécimal. Les chiffres hexadécimaux vont de a ou A à f ou F, et valent respectivement 10 à 15.

On peut ajouter à une constante entière le suffixe u ou U, pour indiquer qu'elle est non signée (unsigned). On peut également lui ajouter le suffixe l ou L pour indiquer qu'elle est de type long.

Le type d'une constante entière dépend de sa forme, de sa valeur et de son suffixe. (les types sont décrits au §4.) Si elle est décimale et ne comprend pas de suffixe, son type est le premier des types suivants dans laquelle elle peut être représentée : int, long int, unsigned long int. Si elle est octale ou hexadécimale et ne comprend pas de suffixe, son type est le premier des types suivants dans laquelle elle peut être représentée : int, unsigned int, long int, unsigned 1ong int. Si elle comprend le suffixe u ou U, son type est unsigned int ou unsigned long int. Si elle comprend le suffixe 1 ou L, son type est long int ou unsigned long int.

La détermination des types des constantes entières est beaucoup plus élaborée que dans la première édition, qui se contentait de donner le type long aux grands nombres. Les suffixes u sont nouveaux.

 

2.5.2 Les constantes de type caractère

Une constante de type caractère est une séquence de un ou plusieurs caractères placés entre apostrophes, comme 'x' . La valeur d'une constante de type caractère ne comprenant qu'un caractère est la valeur numérique de ce caractère dans le jeu de caractères de la machine à l'exécution. La valeur d'une constante à plusieurs caractères dépend de l'implémentation.

Les constantes de type caractère ne peuvent pas contenir le caractère ' ni les caractères de fin de ligne; pour les représenter, ainsi que certains autres caractères, on peut employer les séquences d'échappement suivantes :

fin de ligne            newline              NL (LF) \n
tabulation horizontale  horizontal tab       HT      \t
tabulation verticale    vertical tab         VT      \v
retour en arrière       backspace            BS      \b 
retour chariot          carriage return      CR      \r 
saut de page            formfeed             FF      \f 
signal sonore           audible alert        BEL     \a 
barre oblique           inverse backslash    \       \\
point d'interrogation   question mark        ?       \? 
apostrophe              single quote         '       \'
guillemet               double quote         "       \" 
nombre octal            octal number         ooo     \ooo 
nombre hexadécimal      hexadecimal number   hh      \xhh 

La séquence d'échappement \ooo se compose d'une barre oblique inverse suivie de 1, 2 ou 3 chiffres octaux, qui servent à indiquer la valeur du caractère désiré. Un exemple courant de cette construction est \0 (non suivi d'un chiffre), qui représente le caractère NUL. La séquence d'échappement \xhh se compose d'une barre oblique inverse suivie d'un x, puis de chiffres hexadécimaux, qui servent à indiquer la valeur du caractère désiré. Le nombre de chiffres n'est pas limité, mais le résultat est indéfini si la valeur donnée est supérieur à celle du plus grand caractère. Pour les séquences d'échappement en octal ou en hexadécimal, si le type char de l'implémentation est signé, la valeur subit une extension de signe comme dans le cas d'une conversion dans le type char. Si le caractère qui suit le \ n'est pas l'un de ceux énumérés ci-dessus, le résultat est indéfini.

Certaines implémentations comportent un jeu de caractères étendu que l'on ne peut pas représenter dans le type char. Une constante de ce jeu étendu s'écrit avec le préfixe L, par exemple L'x', et s'appelle une constante de type caractère étendu (wide character constant). Une telle constante est de type wchar_t, un type entier défini dans le fichier d'en-tête standard . Comme pour les constantes de type caractère ordinaires, on peut se servir de séquences d'échappement octales ou hexadécimales ; le résultat est indéfini si la valeur donnée n'est pas représentable dans le type wchar_t.

Certaines de ces séquences d'échappement sont nouvelles, en particulier la représentation hexadécimale des caractères. Les caractères étendus sont également nouveaux. Les jeux de caractères couramment utilisés dans les deux Amériques et en Europe de l'Ouest peuvent tenir dans le type char par un codage convenable ; on a surtout ajouté wchar_t pour pouvoir traiter les caractètes des langues asiatiques.

 

2.5.3 Les constantes flottantes

Une constante flottante se compose d'une partie entière, d'un point décimal, d'une partie fractionnaire, d'un e ou d'un E, d'un exposant entier éventuellement signé, et d'un suffixe de type éventuel, à savoir f, F, l ou L. Chacune des parties entière et fractionnaire se compose d'une séquence de chiffres. On peut omettre la partie entière ou la partie fractionnaire (mais pas les deux) ; on peut aussi omettre le point décimal ou le e suivi de l'exposant (mais pas les deux). Le type est déterminé par le suffixe ; F ou f donne un float, L ou l donne un long double ; sinon, le type est double.

Les suffixes des constantes flottantes sont une nouveauté.

 

2.5.4 Les constantes énumérées

Les identificateurs déclarés comme énumérateurs (voir §8.4) sont des constantes de type int.

2.6 Les constantes de type chaîne

Une constante de type chaîne, que l'on appelle aussi une constante-chaîne, se compose d'une séquence de caractères placés entre guillemets, comme "...". Une chaîne est de type "tableau de caractères" et de classe de stockage s ta t1c (voir ci-dessous §4), et elle est initialisée avec les caractères donnés. Le fait que des constantes de type chaîne identiques soient distinctes ou non dépend de l'implémentation, et le comportement d'un programme qui essaie de modifier une constante de type chaîne est indéfini.

Les constantes de type chaîne adjacentes sont concaténées en une seule chaîne. Après les concaténations éventuelles, le compilateur ajoute à la chaîne un octet nul \0, afin que les programmes qui parcourent cette chaîne puissent en trouver la fin. Les constantes de type chaîne ne peuvent pas contenir de caractères de fin de ligne ni de guillemets ; pour représenter ces caractères, on peut se servir des mêmes séquences d'échappement que pour les constantes de type caractère.

comme pour les constantes de type caractère, les constantes de type chaîne écrites dans un jeu de caractères étendu sont précédées de L, comme L"...". Les constantes de type chaîne de caractères étendus sont de type "tableau de wchar_t ". La concaténation de constantes de type chaîne de caractères ordinaires et étendus est indéfinie.

Le fait que les constantes de type chaîne ne soient pas forcément distinctes, et l'interdiction de les modifier, sont des nouveautés de la norme ANSI, de même que la concaténation de constantes de type chaîne. Les constantes de type châtre de caractères étendus sont également nouvelles.

 

3. La notation syntaxique

Selon la notation syntaxique employée dans cc manuel, les catégories syntaxiques sont imprimées en italique, tandis que les mots et les caractères littéraux sont imprimés dans le style mach ine à é cr ire- Les différentes catégories possibles figurent généralement sur des lignes distinctes ; dans certains cas, une longue série de possibilités est regroupée sur une seule ligne, avec la mention "un parmi". Un symbole facultatif, terminal ou non, porte l'indice "opt", de sorte que, par exemple,

{ expression-opt }

 

représente une expression facultative, placée entre accolades. Nous récapitulons la syntaxe au §13.

Contrairement à la grammaire donnée dans la première édition de ce livre, celle que nous décrivons ici explicite les règles de priorité et d'associativité des opérateurs.

 

4. La signification des identificateurs

Ce que l'on appelle les identificateurs, ou les noms, regroupe en fait plusieurs choses : les fonctions ; les étiquettes de structures, d'unions et d'énumérations ; les membres de structures ou d'unions ; les constantes énumérées ; les noms de définitions de types ; et enfin les objets. Un objet, que l'on appelle parfois une variable, est un emplacement de la mémoire, et son interprétation dépend de deux attributs essentiels : sa classe de stockage et son type. La classe de stockage détermine la durée de vie de la zone mémoire associée à l'objet concerné ; le type détermine la signification des valeurs que l'on trouve dans cet objet Un nom possède aussi une portée, qui est la région du programme où il est connu, et un lien, qui détermine si le même nom dans une autre portée représente le même objet ou la même fonction. Nous abordons les questions de la portée et de l'édition de liens au §11.

4.1 La classe de stockage

Il existe deux classes de stockage : automatique et statique. La classe de stockage d'un objet est précisée par plusieurs mots-clés, ainsi que par le contexte de sa déclaration. Les objets automatiques sont locaux à un bloc (§9.3), et sont effacés lorsqu'on sort de ce bloc. Les déclarations qui figurent à l'intérieur d'un bloc créent des objets automatiques si l'on ne précise pas leur classe de stockage, ou si l'on se sert du spécificateur auto. Les objets déclarés de classe register sont automatiques et sont (si possible) stockés dans les registres rapides de la machine.

Les objets statiques peuvent être locaux à un bloc ou externes à tous les blocs, mais dans les deux cas, ils conservent leur valeur lorsqu'on son des fonctions et des blocs ou quand on y entre à nouveau. A l'intérieur des blocs, y compris les blocs qui contiennent le code d'une fonction, les objets statiques se déclarent grâce au mot-clé static. Les objets déclarés à l'extérieur de tous les blocs, au même niveau que les définitions de fonctions, sont toujours statiques. On peut les rendre locaux à une unité de traduction particulière en ajoutant à leurs déclarations le mot-clé static, ce qui leur donne un lien interne. Ces objets deviennent globaux, utilisables par l'ensemble du programme, si l'on ne précise pas de classe de stockage explicite, ou si l'on se mn du mot-clé extern, ce qui leur donne un lien externe.

4.2 Les types de base

Il existe plusieurs types fondamentaux. Le fichier d'en-tête standard , décrit dans l'annexe B, définit les valeurs extrêmes de chaque type pour l'implémentation utilisée. Les nombres que nous donnons dans l'annexe B représentent les amplitudes minimales acceptables.

Les objets déclarés comme caractères (char) sont assez grands pour mémoriser n'importe quel élément du jeu de caractères d'exécution. Si l'on stocke réellement un caractère de ce jeu dans un objet de type char, sa valeur est celle de l'entier qui code ce caractère, et elle est positive ou nulle. On peut stocker d'autres quantités dans des variables de type char, mais c'est l'implémentation qui détermine quelles sont les valeurs possibles, et surtout le fait que ces valeurs soit signées ou non.

Les caractères non signés, que l'on déclare par unsigned char, prennent la même place que les caractères ordinaires, mais sont toujours positifs ou nuls ; les caractères que l'on déclare explicitement signés, grâce à signed char, prennent également la même place que les caractères ordinaires.

Le type unsiqned char ne figure pas dans la première édition ce livre, mais il est d'usage courant. signed char est une nouveauté.

 

En plus des types char, le C fournit jusqu'à trois types d'entiers de tailles différentes, que l'on déclare grâce à short int, int et long int. Les objets de type int tout court comprennent la taille naturelle des nombres pour l'architecture de la machine utilisée ; les autres tailles permettent de répondre à des besoins particuliers. La taille mémoire des entiers longs est au moins égale à celle des entiers courts, mais c'est l'implémentation qui détermine si les entiers énumérés sont longs ou courts. Les types int représentent tous par défaut des valeurs signées.

Les entiers non signés, que l'on déclare grâce au mot-clé unsigned, suivent les lois de l'arithmétique modulo 2^n, où n est le nombre de bits de la représentation utilisée, par conséquent, les calculs sur des quantités non signées n'induisent jamais de dépassement de capacité. L'ensemble des valeurs positives ou nulles que l'on peut stocker dans un objet signé est un sous-ensemble de celles que l'on peut stocker dans l'objet non signé correspondant, et les valeurs communes à ces deux ensembles se représentent de la même manière.

Certains des types en virgule flottante - à savoir la simple précision (float), la double précision (double), et la précision étendue ( long double) - peuvent être équivalents entre eux, mais les derniers de cette liste sont au moins aussi précis que les précédents.

Le type long double est nouveau. Dans la première édition, long float était équivalents à double ; cette écriture a disparu.

 

Les énumérations sont des types particuliers qui ont des valeurs entières ; à chaque énumération est associée un ensemble de constantes nommées (§8.4). Les énumérations se comportent comme des entiers, mais les compilateurs donnent souvent des avertissements lorsqu'on affecte à un objet d'une certaine énumération, autre chose que l'une de ses constantes ou une expression de son type.

Comme les objets des types décrits ci-dessus peuvent être considérés comme des nombres, nous les appellerons les types arithmétiques. Les types char et int de toutes tailles, signés ou non, ainsi que les énumérations, seront appelés les types entiers. Les types float, double et long double seront appelés les types flottants.

Le type void représente un ensemble vide de valeurs. Il sert de type de retour aux fonctions qui ne produisent pas de valeur.

4.3 Les types dérivés

En plus des types de base, il existe un ensemble théoriquement infini de types dérivés construits à partir des types fondamentaux grâce aux mécanismes suivants :

  • des tableaux d'objets d'un certain type ;
  • des fonctions retournant des objets d'un certain type ;
  • des pointeurs sur des objets d'un certain type ;
  • des structures contenant une séquence d'objets de types divers ;
  • des unions qui peuvent contenir un objet parmi plusieurs de types divers ;

En général, on peut employer ces méthodes de construction d'objets récursivement.

4.4 Les qualificatifs de type

On peut ajouter différents qualificatifs au type d'un objet. Si l'on déclare un objet const, cela indique que sa valeur ne sera jamais modifiée ; si on le déclare volatile, il aura des propriétés particulières en ce qui concerne l'optimisation. Ces qualificatifs ne modifient pas les valeurs possibles d'un objet ni ses propriétés arithmétiques. Les qualificatifs sont décrits au §8.2.

5. Les objets et les valeurs-g

Un objet est une zone mémoire ponant un nom ; une valeur-g est une expression qui fait référence à un objet. Un exemple trivial d'expression valeur-g est un identificateur ayant le bon type et la bonne classe de stockage. Certains opérateurs produisent des valeurs-g : par exemple, si E est une expression de type pointeur, *E est une expression valeur-g qui fait référence à l'objet pointé par E. Le nom "valeur-g" provient de l'expression d'affectation El = E2, où l'opérande de gauche, El, doit être une expression valeur-g. Dans la description de chaque opérateur, nous précisons s'il a besoin d'une valeur-g et s'il produit une valeur-g.

6. les Conversions

Certains opérateurs peuvent, selon leurs opérandes, provoquer la conversion d'un type à un autre de la valeur d'un opérande. Cette section explique les résultats à attendre de telles conversions. Le §6.5 récapitule les conversions imposées par les opérateurs les plus courants ; le complément à ces informations se trouve dans la description de chaque opérateur.

6.1 La promotion entière

On peut employer un caractère, un entier const, ou un champ de bits entier, signés ou non, ou encore un objet de type énumération, partout où l'on peut employer un entier dans une expression. Si un int peut représenter toutes les valeurs du type d'origine, la valeur est convertie en int ; sinon, elle est convertie en unsigned int. Ce processus s'appelle la promotion entière.

6.2 Les conversions entières

Pour convenir un entier quelconque dans un certain type non signé, on cherche la plus petite valeur positive ou nulle qui soit congrue à cet entier, modulo la plus grande valeur représentable dans ce type non signé plus un. Dans une représentation par complément à deux, ceci équivaut à tronquer la partie gauche de la valeur si le type non signé est plus court (c'est-à-dire s'il se représente sur moins de bits que la valeur à convertir), ou, s'il est plus long, à compléter la valeur par des zéros si elle est non signée, ou par des bits de signe si elle est signée.

Lorsqu'un entier quelconque est converti en un type signé, sa valeur est inchangée si elle est représentable dans ce type ; sinon, elle est définie par l'implémentation.

6.3 Les entiers et les flottants

Lorsqu'une valeur de type flottant est convertie en un type entier, sa partie fractionnaire est effacée ; si la valeur qui en résulte n'est pas représentable dans le type entier, le résultat est indéfini. En particulier, le résultat de la conversion d'une valeur flottante négative en un type entier non signé n'est pas défini.

Lorsqu'un valeur de type entier est convertie en flottant, et si elle n'est pas représentable exactement bien qu'elle soit i à l'intérieur du domaine de représentation du type flottant, le résultat est la valeur représentable la plus proche, soit par défaut, soit par excès. Si la valeur n'est pas dans le domaine de représentation, le résultat est indéfini.

6.4 Les types flottants

Lorsqu'une valeur flottante est convertie en un type flottant plus précis, elle est inchangée. Lorsqu'elle est convertie dans un type flottant moins précis, et si elle est dans le domaine de représentation de ce type, le résultat est la valeur représentable la plus proche, soit par défaut, soit par excès. Si la valeur n'est pas dans le domaine de représentation, le résultat est indéfini.

6.5 Les conversions arithmétiques

De nombreux opérateurs provoquent des convenions et donnent un certain type de résultat selon les mêmes règles. Cela permet de transformer leurs opérandes en un type commun, qui sera celui du résultat. Ces règles s'appellent les conversions arithmétiques usuelles.

  • D'abord, Si l'un des opérandes est de type long double, convertir l'autre en long double.
  • Sinon, si l'un des opérandes est de type double, convertir l'autre en double.
  • Sinon, si l'un des opérandes est de type float, convenir l'autre en float.
  • Sinon, appliquer les promotions entières aux deux opérandes ; puis, si l'un des opérandes est de type unsigned long int, convenir l'autre en unsigned long int.
  • Sinon, si l'un des opérandes est de type long int et l'autre de type unsigned int, le résultat dépend du fait qu'un long int puisse représenter ou non toutes les valeurs d'un unsigned int ; si oui, convenir l'opérande de type unsigned int en long int ; si non, convenir les deux opérandes en unsigned long int.
  • Sinon, si l'un des opérandes est de type long int, convenir l'autre en long int,
  • Sinon, si l'un des opérandes est de type unsigned int, convertir l'autre en unsigned int.
  • Sinon, les deux opérandes sont de type int.
Deux changements dans ces règles : premièrement, les calculs sur les opérandes de type float peuvent s'effectuer en simple précision, et non en double ; la première édition stipulait que tous les calculs en virgule flottante s'effectuaient en double précision. Deuxièmement, si l'on combine un type non signé avec un type signé plus long, le résultat est signé ; dans la première édition, la propriété "non signé" était toujours dominante. Ces nouvelles règles sont légèrement plus compliquées, mais elles réduisent nettement les résultats surprenant obtenus auparavant lorsqu'on combinait les quantités signée et non signée. Il est encore possible d'obtenir des résultats inattendus si l'on compare une expression non signée à une expression signée de même taille.

6.6 Les pointeurs et les entiers

On peut ajouter ou soustraire une expression de type entier à un pointeur ; dans ce cas, l'expression entière est convertie comme indiqué dans la description de l'opérateur d'addition (§7.7).

On peut soustraire l'un de l'autre deux pointeurs sur des objets de même type contenus dans le même tableau ; le résultat est converti en un entier comme indiqué dans la description de l'opérateur de soustraction (§7.7).

On peut convenir une expression constante entière valant 0, ou une telle expression convertie en void*, en un pointeur de type quelconque, par l'intermédiaire d'une conversion de type, d'une affectation ou d'une comparaison. Le résultat est un pointeur nul égal à un autre pointeur nul du même type, mais différent de tout pointeur sur une fonction ou un objet.

Certaines autres conversions concernant les pointeurs sont autorisées, mais elles dépendent de l'implémentation. Il faut les indiquer par un opérateur de conversion de type explicite (§§7.5 et 8.8).

On peut convertir un pointeur en un type entier assez long pour le contenir ; la taille nécessaire dépend de l'implémentation, ainsi que la fonction de transcodage.

On peut convenir explicitement un objet de type entier en un pointeur. Le transcodage transforme toujours un entier suffisamment long obtenu à partir d'un pointeur donné en ce même pointeur, mais ses autres caractéristiques dépendent de l'implémentation.

On peut convenir un pointeur en un autre pointeur, de même type, aux qualificatifs de l'objet pointé près (§§4.4, 8.2). Si l'on ajoute des qualificatifs, le nouveau pointeur est équivalent à l'ancien, mais il subit les restrictions imposées par les nouveaux qualificatifs. Si l'on en supprime, les opérations effectuées sur l'objet pointé restent soumises aux qualificatifs figurant dans sa déclaration originelle.

Enfin, on peut convertir un pointeur sur une fonction en un pointeur sur un autre type de fonction. Le résultat de l'appel de la fonction pointée par le pointeur converti dépend de l'implémentation ; toutefois, si l'on reconvertit le pointeur dans son type d'origine, il se comporte comme le pointeur original.

6.7 Le type void

On ne peut pas employer n'importe comment la valeur (inexistante) d'un objet de type void (vide), et on ne peut pas lui appliquer de conversion, implicite ou explicite, dans un type autre que void. Puisqu'une expression de type void représente une valeur inexistante, on ne peut s'en servir que si l'on n'a pas besoin de sa valeur, par exemple en tant qu'instruction-expression (§9.2) ou en tant qu'opérande gauche d'un opérateur virgule (§7.18).

On peut convertir une expression dans le type void par une conversion de type explicite. Par exemple, une conversion en void indique que l'on abandonne la valeur d'un appel de fonction servant d'instruction-expression.

Le type void ne figurait pas dans la première édition de ce livre, mais il était d'usage courant.

 

6.8 Les pointeurs sur Void

On peut convertir un pointeur quelconque dans le type void * sans perte d'information. Si l'on reconvertit le résultat dans le type d'origine du pointeur, on retrouve le pointeur originel. Contrairement aux conversions de pointeur à pointeur abordées au §6.6, qui nécessitent une conversion de type explicite, on peut réaliser directement des affectations et des comparaisons entre des pointeurs sur des objets et des pointeur de type void*.

Cette interprétation des pointeurs de type void* est nouvelle ; auparavant c'étaient les pointeurs de type char* qui jouaient le rôle de pointeurs génériques. La norme ANSI autorise explicitement la combinaison de pointeurs sur des objets et de pointeurs de type void*, alors qu'elle impose d'écrire des "casts" explicie pour les autres mélanges de pointeurs.

 

7. Les expressions

Les grands paragraphes de cette section sont présentés dans l'ordre décroissant des priorités des opérateurs dans les expressions. Ainsi, par exemple, les expressions mentionnées comme opérandes de + (§7.7) sont les expressions définies dans les §§7.1-7.6. A l'intérieur de chaque paragraphe, les opérateurs ont le même degré de priorité. L'associativité à gauche ou à droite de chaque opérateur est précisée dans son paragraphe. La grammaire présentée au §13 spécifie la priorité et l'associativité des opérateurs.

La priorité et l'associativité des opérateurs sont totalement définies, mais l'ordre d'évaluation des expressions ne l'est pas, à quelques exceptions près, même si les sous-expressions entraînent des effets de bord. Par conséquent, sauf si la définition d'un opérateur garantit que ses opérandes sont évalués dans un certain ordre, c'est l'implémentation qui choisit l'ordre d'évaluation des opérandes. Toutefois, chaque opérateur combine les valeurs engendrées par ses opérandes d'une manière compatible avec l'analyse grammaticale de l'expression où il figure.

Cette régle interdit désormais de modifier l'ordre d'évaluation des expressions comprenant des opérateurs commutatifs et associatifs mathématiquement, mais pas forcément associatifs informatiquement En pratique, ce changement ne conceme que les calculs en virgule flottante aux alentours des limites de précision, et les situations qui peuvent provoquer un dépassement de capacité.

 

Le traitement des dépassements de capacité, des divisions par zéro et des autres exceptions qui peuvent intervenir au cours de l'évaluation des expressions, n'est pas défini par le langage. La plupart des implémentations du C existantes ne tiennent pas compte des dépassements de capacité pour évaluer les expressions entières signées, ni pour les affectations, mais ce comportement n'est pas garanti. Le traitement de la division par zéro, et de toutes les exceptions en virgule flottante, dépend de l'implémentation ; on peut parfois le contrôler via une bibliothèque de fonctions non standard.

7.1 La génération de pointeurs

Si une expression ou une sous-expression est de type "tableau de T", où T est un type donné, alors la valeur de cette expression est un pointeur sur le premier élément du tableau, et le type de l'expression se transforme en "pointeur sur T". Cette conversion n'a pas lieu si l'expression est l'opérande d'un des opérateurs unaires &, ++, -- ou sizeof, ou l'opérande de gauche d'un opérateur d'affectation ou de l'opérateur ".". De même, une expression de type "fonction retoumant T" est convertie en un "pointeur sur une fonction retournant T", sauf lorsqu'elle sert d'opérande à un opérateur &.

7.2 Les expression primaires

Les expressions primaires sont les identificateurs, les constantes, les chaînes de caractères et les expressions entre parenthèses.

expression-primaire :

 

identificateur
constante
constante-caractères

( expression )

 

Un identificateur est une expression primaire, du moment qu'il a été déclaré convenablement, comme indiqué plus loin. Son type est déterminé par sa déclaration. Un identificateur est une valeur-g s'il fait référence à un objet (§5) et s'il est de type arithmétique, structure, union ou pointeur.

Une constante est une expression primaire. Son type dépend de sa forme, comme indiqué au §2.5.

Une constante de type chaîne est une expression primaire, Elle est tout d'abord de type "tableau de char" (ou, pour les chaînes de caractère étendus, "tableau de wchar_t "), mais d'après la règle donnée au §7.1, son type devient généralement Un "pointeur sur char" (ou sur wchar_t), et le résultat est un pointeur sur le premier caractère de la chaîne. Néanmoins, cette conversion n'a pas lieu dans le cas de certains initialisateurs (cf §8.7).

Une expression entre parenthèses est une expression primaire dont le type et la valeur sont identiques à ceux de l'expression intérieure. La présence de parenthèses ne modifie pas le fait que l'expression soit une valeur- g ou non.

7.3 Les expressions postfixées

Les opérateurs des expressions postfixées s'évaluent de gauche à droite.

expression-postfixée :

 

expression-primaire
expression-postfixée

[ expression ]
expression-postfixée ( liste-d'expessions-en-arguments-opt )
expession-postfixée . identificateur
expression-postfixée -> identificateur
expression-posfixée ++
expression-postfixée --

 

liste-d'expessions-en-arguments :

expression-d'affectation
liste-d'expressions-en-arguments
, expression-d'affectation

 

7.3.1 Les références aux tableaux

Une expression postfixée suivie d'une expression placée entre crochets est une expression postfixée représentant une référence à un tableau indexé, L'une des deux expressions doit être de type "pointeur sur T", où T est un type quelconque, et l'aune doit être de type entier ; le type de l'expression indexée est T, L'expression E1[E2] est équivalente (par définition) à *((E1)+(E2)) , Reportez-vous au §8.6.2 pour de plus amples détails.

7.3.2 Les appels de fonctions

Un appel de fonction se compose d'une expression postfixée, appelée le désignateur de fonction, suivie de parenthèses contenant une liste, éventuellement vide, d'expressions d'affectation séparées par des virgules. (§7.17), qui constituent les arguments de la fonction. Si cette expression postfixée est un identificateur dont il ' n'existe pas de déclaration dans la portée courante, cet identificateur est déclaré implicitement, comme si l'on avait fait figurer la déclaration

extern int identificateur ();

 

dans le bloc où se trouve l'appel de fonction. L'expression postfixée (après d'éventuelles déclarations implicites et générations de pointeurs, cf§7.1) doit être de type "pointeur sur une fonction retoumant T", où T est un type quelconque, et la valeur de l'appel de fonction est de type T.

Dans la première édition, le seul type possible était "fonction", et il fallait employer un opérateur * explicite pour appeler une fonction via un pointeur. La norme ANSI officialise ce que certains compilateurs permettaient déjà, à savoir une syntaxe identique pour les appels de fonctions directs ou via des pointeurs. L'ancienne syntaxe est toujours utilisable.

 

Le terme argument désigne une expression passée à une fonction lors d'un appel; le terme paramètre désigne un objet (ou son identificateur) reçu par une définition de fonction ou décrit dans une déclaration de fonction. On emploie parfois respectivement les termes "argument (ou paramètre) effectif" et "argument (ou paramètre) formel" pour distinguer ces deux concept.

Lors de la préparation d'un appel de fonction, chaque argument est copié ; tous les passages d'arguments se font par valeur. Une fonction peut modifier les valeurs de ses objets paramètres, qui sont des copies des expressions arguments, mais ces modifications ne peuvent pas toubher les valeurs des arguments. Toutefois, il est possible de passer un pointeur à une fonction, s'il est bien entendu que celle-ci a le droit de modifier la valeur de l'objet pointé par ce pointeur.

On peut déclarer les fonctions sous deux formes différentes. Selon la nouvelle forme, les type des paramètres sont explicites et font partie du type de la fonction ; une telle déclaration s'appelle aussi un prototype de fonction. Selon l'ancienne forme, les types des paramètres ne sont pas précisés. Les déclarations de fonctions sont décrites aux §§8.6.3 et 10.1,

Si, lors d'un appel, la déclaration de fonction dont la portée recouvre cet appel est sous l'ancienne forme, on applique à chaque argument une promotion, de la manière suivante : on applique la promotion entière (§6.1) à chaque argument de type entier, et on convertit chaque argument de type f lo~ t en doub1e. Lc résultat de l'appel est indéfini si le nombre d'arguments est différent du nombre de paramètres figurant dans la définition de la fonction, ou si le type d'un argument, après promotion, est différent du type du paramètre correspondant. La cohérence des types dépend de la forme de la définition de la fonction. Dans le cas de l'ancienne forme, on compare le type de l'argument, après promotion, au type du pararnètre, après promotion ; dans le cas de la nouvelle forme, l'argument, après promotion, doit être de même type que le paramètre, sans promotion.

Si la déclaration de fonction dont la portée recouvre l'appel est sous la nouvelle forme, les arguments sont convertis dans les types des paramètres correspondants du prototype de la fonction, comme pour les affectations. Le nombre d'arguments doit être égal au nombre de paramètres décrits explicites, sauf si liste de paramètres de la déclaration se termine par la notation d'ellipse (,...). Dans ce cas, le nombre d'arguments doit être supérieur ou égal au nombre de paramètres ; les arguments supplémentaires, figurant au-delà des paramètres décrits explicitement, subissent la promotion par défaut des arguments, comme indiqué ci-dessus. Si la fonction est définie sous l'ancienne forme, chaque paramètre du prototype visible lors de l'appel doit être de même type que le paramètre correspondant de la définition, après que celui-ci ait subi la promotion d'argument.

Ces règles sont particulièrement compliquées à cause du mélange entre les fonctions sous l'ancienne et sous la nouvelle forme. Il vaut mieux éviter de tels mélanges si possible.

 

L'ordre d'évaluation des arguments n'est pas défini ; notez bien qu'il varie selon les compilateurs. Toutefois, les arguments et le désignateur de fonction sont entièrement évalués, y compris leurs effets de bord, avant d'entrer dans la fonction. Les appels récursifs sont autorisés pour toutes les fonctions.

7.3.3 Les références aux structures

Une expression postfixée suivie d'un point et d'un identificateur est une expression postfixée. Sa première expression opérande doit être une structure ou une union, et l'identificateur doit être le nom d'un membre de cette structure ou de cette union. La valeur de cette expression et son type sont ceux du membre désigné par son nom dans la structure ou l'union. Cette expression est une valeur-g si la dernière expression en est une, et si la seconde n'est pas de type tableau.

Une expression postfixée suivie d'une flèche (formée d'un - et d'un >) et d'un identificateur est une expression postfixée. Sa première expression opérande doit être un pointeur sur une suucture ou une union, et l'identificateur doit être le nom d'un membre de cette structure ou de cette union. Le résultat fait référence au membre désigné par son nom dans la structure ou l'union pointée par l'expression de type pointeur. Le type du résultat est le type de ce membre ; le résultat est une valeur- g s'il n'est pas de type tableau.

Ainsi, l'expression E1->MDS équivaut à (*E1).MDS. Les structures et les unions sonl décrites au §8.3.

Dans la première édition de ce livre, le nom du membre désigné dans une telle expression devait déjà appartenir à la structure ou l'union donnée dans l'expression postfixée ; toutefois, une note précisait que cette régie n'était pas absolue. Les compilateurs récents, ainsi que la norme ANSI, l'imposent effectivement.

 

7.3.4 L'incrémentatlon postfixée

Une expression postfixée suivie d'un opérateur ++ ou -- est une expression postfixée. La valeur d'une telle expression est celle de son opémdôe. Après avoir prts note de la valeur, on ajoute (++) ou on retranche (--) 1 à l'opérande (cette opération s'appelle aussi l'incrémentàtion ou la décrémentation, respectivement). L'opérande doit être une valeur-g ; reportez-vous à la description des opérateurs additifs (§7.7) et d'affectation (§7.17) pour connaître les autres contraintes sur l'opérande et les détails de l'opération. Le résultat n'est pas une valeur-g.

7.4 Les opérateurs unaires

Les expressions componant des opérateurs unaires s'évaluent de droite à gauche. ,

expression-unaire :

 

expression-postfixée
++ expression-unaire
-- eprression-unaire
opérateur-unaire expression-conversion

sizeof expression-unaire
sizeof ( nom-de-type )

 

opérateurs unaires: un parmi

& * + - ~!

 

7.4.1 Les opérateurs d'incrémentation préfixés

Une expression unaire précédée d'un opérateur ++ ou -- est une expression unaire. Son opérande est incrémenté (++) ou décrémenté (--). Sa valeur est prise après l'incrémentation (ou la décrémentation). L'opérande doit être une valeur-g ; reportez-vous à la description des opérateurs additifs (§7.7) et d'affectation (§7.17) pour connaître les autres contraintes sur l'opérande et les détails de l'opération. Le résultat n'est pas une valeur-g.

7.4.2 L'opérateur de prise d'adresse

L'opérateur unaire & prend l'adresse de son opérande, qui doit être soit une valeur-g ne faisant pas référence à un champ de bits ni à un objet de classe register, soit de type fonction. Le résultat est un pointeur sur l'objet ou la fonction désigné par cette valeur-g. Si l'opérande est de type T, le résultat est de type "pointeur sur T".

7.4.3 L'opérateur d'indirection

L'opérateur unaire * indique une indirection, et retoume l'objet ou la fonction pointé par son opérande. C'est une valeur-g si l'opérande est un pointeur sur un objet de type arithmétique, structure, union ou pointeur. Si l'expression est de type "pointeur sur T", le résultat est de type T.

7.4.4 L'opérateur plus unaire

L'opérande de l'opérateur + unaire doit être de type arithmétique, et le résultat est la valeur de cet opérande. Un opérande entier subit la promotion entière. Le résultat est du type de l'opérande après promotion.

L'opérateur + unaire est une nouveauté de la norme ANSI. Il a été ajouté par souci de symétrie avec le - unaire.

 

7.4.5 L'opérateur moins unaire

L'opérande de l'opérateur - unaire doit être de type arithmétique, et le résultat est l'opposé de cet opérande. Un opérande entier subit la promotion entière. L'opposé d'une quantité non signée se calcule en soustrayant la valeur après promotion de la plus grande valeur du type résultant de la promotion, et en ajoutant un ; toutefois, l'opposé de zéro est toujours zéro. Le résultat est du type de l'opérande après promotion.

7.4.6 L'opérateur de complément à un

L'opérande de l'opérateur ~ doit être de type entier, et le résultat est le complément à un de cet opérande. L'opérande subit la promotion entière. Si l'opérande est non signé, le résultat se calcule en soustrayant la valeur de la plus grande valeur du type résultant de la promotion. Si l'opérande est signé, le résultat se calcule en convertissant l'opérande après promotion dans le type non signé correspondant, en lui appliquant ~, puis en le reconvertissant dans le type signé. Le résultat est du type de l'opérande après promotion.

7.4.7 L'opérateur de négation logique

L'opérande de l'opérateur ! doit être de type arithmétique ou pointeur, et le résultat vaut 1 si cet opérande vaut 0, et 0 dans tous les autres cas. Le résultat est de type int.

7.4.8 L'opérateur sizeof

L'opérateur sizeof donne le nombre d'octets nécessaires pour mémoriser un objet du type de son opérande. Cet opérande est soit une expression, qui n'est pas évaluée, soit un nom de type entre parenthèses. Lorsqu'on applique s izeof à un char, le résultat vaut 1 ; lorsqu'on l'applique à un tableau, le résultat est le nombre total d'octets qu'occupe ce tableau. Lorsqu'on l'applique à une structure ou à une union, le résultat est le nombre d'octets de cet objet, y compris les octets de remplissage éventuels servant à le superposer à un tableau : la taille d'un tableau de n éléments vaut n fois la taille d'un élément. On ne peut pas appliquer cet opérateur à un opérande de type fonction ou de type incomplet, ni à un champ de bits. Le résultat est une constante entière non signée ; son type particulier dépend de l'implémentation. Le fichier d'en-tête standard définit ce type et l'appelle size_t.

7.5 L'opérateur "cast" de conversion de type

Une expression unaire précédée d'un nom de type entre parenthèses provoque la conversion de sa valeur en une expression du type indiqué.

expession-conversion :

 

expression-unaire
( nom-de-type ) expression-conversion

 

Cette construction s'appelle une conversion de type (ou cast). Les noms de type sont décrits au §8.8, et les effets des conversions au §6. Une expression comportant une conversion de type n'est pas une valeur-g.

7.6 Les opérateurs multiplicatifs

Les opérateurs multiplicatifs *, / et % s'éva1uent de gauche à droite

expression-multiplicative :

 

expression-conversion
expression-multiplicative
* expression-conversion
expression-multiplicative / expression-conversion
expression-multiplicative % expression-conversion

 

Les opérandes de * et / doivent être de type arithmétique ; ceux de %, de type entier. Les opérandes subissent les conversions arithmétiques usuelles, qui fixent également le type du résultat.

L'opérateur binaire * indique la multiplication.

L'opérateur binaire / donne le quotient, et l'opérateur % le reste, de la division du premier opérande par le second ; si le second opérande vaut 0, le résultat est indéfini. Sinon, l'expression (a/6)*b+a%b vaut toujours a. Si les deux opérandes sont positifs ou nuls, le reste est positif ou nul, et inférieur au diviseur ; sinon, le langage garantit seulement que la valeur absolue du reste est inférieure à celle du diviseur.

7.7 Les opérateurs additifs

Les opérateurs additifs + et - s'évaluent de gauche à droite. Si les opérandes sont de type arithmétique, ils subissent les conversions arithmétiques usuelles. Chacun de ces opérateurs accepte certains autres types d'opérandes.

expression-additive :

 

expression-multiplicative
expression-additive
+ expression-multiplicative
expression-additive - expression-multiplicative

 

L'opérateur + donne la somme de ses opérandes. On peut additionner un pointeur sur un objet d'un tableau et une valeur d'un type entier quelconque. Cette dernière est convertie en un déplacement d'adresse en la multipliant par la taille de l'objet pointé par le pointeur. La somme est alors un pointeur du même type, qui pointe sur un autre objet du même tableau, décalé convenablement par rapport à l'objet de départ. Ainsi, si p pointe sur un certain objet d'un tableau, l'expression p+1 pointe sur l'objet suivant du tableau. Si la somme pointe à l'extérieur des bornes du tableau, le résultat est indéfini, sauf dans le cas de la position suivant immédiatement la borne supérieure.

La possibilité de pointer un élément plus loin que Ia fin d'un tableau est une nouveauté. Elle rend légitimes des éritures courantes permettant d'exécutcr une boucle sur tous les éléments d'un tableau.

 

L'opérateur - donne la différence de ses opérandes. On peut soustraire une valeur d'un type entier quelconque d'un pointeur, dans les mêmes conditions que pour l'addition, et avec les mêmes conversions.

Si l'on soustrait deux pointeurs sur des objets de même type, le résultat est une valeur entière signée représentant le décalage entre les objets pointés, étant donné que les pointeurs sur des objets successifs différent de 1. Le type du résultat dépend de l'implémentation, mais il est défini dans le ficher d'en-tête standard et s'appelle ptrdiff_t. La valeur du résultat est indéfinie si les objets pointés ne font pas partie du même tableau ; toutefois, si p pointe sur le dernier objet d'un tableau, l'expression (p+1)-p vaut 1.

7.8 Les opérateurs de décalage

Les opérateurs de décalage " et " s'évaluent de gauche à droite. Leurs opérandes doivent être de type entier, et subissent les pmmotions entières. Le résultat est du type de l'opérande de gauche, après promotion. Le résultat est indéfini si l'opérande de droite est négatif, ou supérieur ou égal au nombre de bits du type de j'expression de gauche,

expression-de-décalage :

 

expression-additive
expression-de-décalage
<< expression-additive
expression-de-décalage
>> eexpression-additive

 

La valeur de E1< est égale à E1 (vue comme une séquence de bits) décalée de E2 bits vers la gauche ; s'il n'y a pas de dépassement de capacité, cette opération équivaut à une multiplication par 2^n. La valeur de E1>>E2 est égale à E1 (décalée de E2 bits vers la droite ; le décalage à droite équivaut à une division par 2^n si E1 est non signée, ou bien positive ou nulle ; sinon, le résultat dépend de l'implémentation.

7.9 Les opérateurs relationnels

Les opérateurs relationnels s'évaluent de gauche à droite, mais cela n'a pas d'importance ; l'analyse de a donne (a, et a vaut soit 0, soit 1.

expression-relationnelle :

 

expression-de-décalage
expression-relationnelle
< expression-de-décalage
expression-relationnelle
> expression-de-décalage
expression-relationnelle
>= expression-de-décalage
expression-relationnelle
<= expression-de-décalage

 

Les opérateurs < (inférieur), > (supérieur), <= (inférieur ou égal) et >= (supérieur ou égal) donnent tous 0 si la relation indiquée est fausse, ou bien 1 si elle est vraie. Le résultat est de type int. Les opérandes arithmétiques subissent les conversions arithmétiques usuelles. On peut comparer des pointeurs sur des objets de même type ; le résultat dépend des positions relatives des objets pointés dans l'espace d'adressage.

La comparaison de pointeurs n'est définie que pour des éléments du même objet : si deux pointeurs pointent sur le même objet simple, ils sont égaux ; s'ils pointent sur des membres de la même structure, les plus grands sont ceux qui pointent sur des objets déclarés plus loin dans la structure ; s'ils pointent sur des membres de la même union, ils sont égaux ; s'ils pointent sur des éléments d'un tableau, leur comparaison équivaut à celle des indices correspondants. Si p pointe sur le dernier élément d'un tableau, p+1 est supérieur à p, bien qu'il pointe en dehors du tableau. Dans les autres cas, la comparaison de pointeurs est indéfinie.

Ces règles assouplissent légèrement les contraintes exposées dans la première édition, en autorisant la comparaison de pointeurs sur des éléments différents d'une structure ou d'une union, ainsi que la comparaison avec un pointeur pointant juste derrière la fin d'un tableau. .

 

7.10 Les opérateurs d'égalité

expression-d'égalité :

 

expression-relationnelle
expression-d'égalité
== expression-relationnelle
expression-d'égalité
!= expression-relationnelle

 

Les opérateurs == (égal à) et != (différent de) sont analogues aux opérateurs relationnels, mis à part que leur degré de priorité est plus faible. (Ainsi, l'expression a vaut 1 si a et c ont la même valeur logique. ) Les opérateurs d'égalité obéissent aux mêmes règles que les opérateurs relationnels, mais ils offrent d'autres possibilités : on peut comparer un pointeur à une expression en6ère constante valant 0, ou à un pointeur sur vo id (cf §6.6).

7.11 L'opérateur ET bit à bit

expression-ET :

 

expression-d'égalité
expression-ET
& expression-d'égalité

 

Les opérandes subissent les conversions arithmétiques usuelles ; le résultat est la combinaison des opérandes par la fonction "ET" appliquée bit par bit. Cet opérateur ne s'applique qu'aux opérandes de type entier.

7.12 L'opérateur OU exclusif bit à bit

expression-OU-exclusive :

 

expression-ET
expression-OU-exclusive
^ expression-ET

 

Les opérandes subissent les conversions arithmétiques usuelles ; le résultat est la combinaison des opérandes par la fonction "OU exclusif" appliquée bit par bit. Cet opérateur ne s'applique qu'aux opérandes de type entier.

7.13 L'opérateur OU inclusif bit à bit

expression-OU-inclusive :

 

expression-OU-exclusive
expression-OU-inclusive
| expression-OU-exclusive

 

Les opérandes subissent les conversions arithmétiques usuelles ; le résultat est la combinaison des opérandes par la fonction "OU inclusif" appliquée bit par bit. Cet opérateur ne s'applique qu'aux opérandes de type entier.

7.14 L'opérateur ET logique

expression-ET-logique :

 

expression-OU-inclusive
expression-ET-logique
&& expression-OU-inclusive

 

L'opérateur && s'évalue de gauche à droite. Il donne 1 si ses deux opérandes sont différents de zéro, ou bien 0 dans les autres cas. Contrairement à l'opérateur &, && garantit que l'évaluation s'effectue de gauche à droite : le premier opérande est évalué, y compris ses effets de bord ; s'il vaut 0, l'expression vaut 0. Sinon, l'opérande de droite est évalué, et l'expression vaut 0 s'il vaut 0, ou 1 dans le cas contraire.

Les opérandes ne sont pas nécessairement du même type, mais ils doivent tous deux être de type arithmétique ou pointeur. Le résultat est de type int.

7.15 L'opérateur OU logique

expression-OU-logique :

 

expression-ET-logique
expression-OU-logique
|| expression-ET-logique

 

L'opérateur || s'évalue de gauche à droite. Il donne 0 si ses deux opérandes valent zéro, ou bien 1 dans les autres cas. Contrairement à |, || garantit que l'évaluation s'effectue de gauche à droite : le premier opérande est évalué, y compris ses effets de bord ; s'il est différent de 0, l'expression vaut 1. Sinon, l'opérande de droite est évalué, et l'expression vaut 1 s'il est différent de 0, ou 0 dans le cas contraire.

Les opérandes ne sont pas nécessairement du même type, mais ils doivent tous deux être de type arithmétique ou pointeur. Le résultat est de type int.

7.16 L'opérateur conditionnel

expression-conditionnelle :

 

expression-OU-logique
expression-OU-logique
? expression : expression-conditionnelle

 

La première expression est évaluée, y compris ses effets de bord ; si elle est différente de 0, le résultat est la valeur de la deuxième expression, sinon celle de la troisième. on n'évalue qu'un seul opérande parmi le deuxième et le troisième. Si le deuxième et. le troisième opérandes sont arithmétiques, ils subissent les convenions arithmétiques usuelles, ce qui les transforme en un type commun, qui est le type du résultat. Si ces deux opérandes sont de type void, ou bien des structures ou des unions du même type, ou encore des pointeurs sur des objets de même type, le résultat prend ce type commun. Si l'un de ces opérandes est un pointeur et l'autre la constante 0, le 0 est converti dans le type du pointeur, qui devient celui du résultat. Si l'un est un pointeur sur void et l'autre est un autre pointeur, Ce demier est converti en un pointeur sur void, et c'est là le type du résultat.

En ce qui conceme la comparaison de pointeurs, les qualificatifs éventuels (§8.2) du type pointé sont sans importance, mais le type du résultat hérite des qualificatifs venant des deux branches de l'expression conditionnelle.

7.17 Les expressions d'affectation

Il existe plusieurs opérateurs d'affectation ; ils s'évaluent tous de droite à gauche.

expression-d'affectation :

 

expression-conditionnelle
expression-unaire opérateur-d'affectation expression-d'affectation

 

opérateur-d'affectation : un parmi

= *= /= %= += -= <<= >>= &= ^= |=

 

Pour tous ces opérateurs, l'opérande de gauche doit être une valeur-g modifiable : il ne peut pas être de type tableau ni de type incomplet, ni être une fonction. De plus, son type ne doit pas porter le qualificatif const ; si c'est une structure ou une union, aucun de ses membres et de ses sous-membres, récursivement, ne doit être qualifié par const. Une expression d'affectation prend le type de son opérande de gauche, et sa valeur après affectation.

Dans le cas de l'affectation simple par =, la valeur de l'expression remplace celle de l'objet désigné par la valeur-g. L'une des conditions suivantes doit être vérifiée : les deux opérandes sont de type arithmétique, auquel cas l'affectation convertit celui de droite dans le type de celui de gauche ; ou bien les deux opérandes sont des structures ou des unions du même type ; ou bien l'un des opérandes est un pointeur est l'autre un pointeur sur void ; ou encore l'opérande de gauche est un pointeur et celui de droite est une expression constante valant 0 ; ou enfin les deux opérandes sont des pointeurs sur des fonctions ou des objets de même type, mis à part que l'opérande de droite peut se passer des qualificatifs const ou volatile caractérisant éventuellement celui de gauche.

Une expression de la forme E1 op= E2 équivaut à E1 = E1 op (E2) , mis à part que E1 n'est évaluée qu'une seule fois.

D'après les contraintes ci-dessus, il est interdit de réaliser des affectations de pointeurs lorsque la partie droite pointe sur un objet d'un certain type, et la partie gauche sur un objet du type qualifié par const correspondant. Prise à la lettre, cette règle, ainsi que la règle équivalente concernant les conversions de types, entraîne des difficultés pour implémenter oertaines fonctions de la bibliothéque ; cela consstitue une bonne raison pour l'assouplir.

 

7.18 L'opérateur virgule

expression :

 

expression-d'affectation
expression
, expression-d'affectation

 

Deux expressions séparées par une virgule s'évaluent de gauche à droite, et la valeur de l'expression de gauche est perdue. Le résultat prend le type et la valeur de l'opérande de droite. Tous les effets de bord de l'évaluation de l'opérande de gauche sont exécutés avant de commencer à évaluer l'opérande de droite. Dans les contextes où la virgule a un sens spécial, par exemple dans les listes d'arguments de fonctions (§7.3.2) et les listes d'initialisateurs (§8.7), l'unité syntaxique nécessaire est une expression d'affectation, si bien que l'opérateur virgule ne peut figurer que dans un groupement entre parenthèses ; par exemple,

f(a, (t=3, t+2), c)

 

a trois arguments et le deuxième d'entre eux vaut 5.

7.19 Les expressions constantes

S yntaxiquement, une expression constante est une expression ne comportant que certains opérateurs :

expression -constante :

 

expression -conditionnelle

 

Les expressions qui prennent des valeurs constantes sont nécessaires dans divers contextes : après case, en tant que bornes de tableaux et longueurs de champs de bits, comme valeurs de constantes énumérées, dans les initialisateurs et dans certaines expressions du préprocesseur.

Les expressions constantes ne peuvent pas contenir d'affectations, d'opérateurs d'incrémentation ou de décrémentation, d'appels de fonctions, ni d'opérateurs virgule, sauf en tant qu'opérandes de sizeof. Si l'expression constante doit être de type entier, ses opérandes doivent être constitués exclusivement de constantes entières, énumérées, de type caractère, et flottantes ; les conversions de types éventuelles doivent donner des types entiers, et il faut convertir toutes les constantes flottantes en entiers. Ces règles interdisent automatiquement les opérations sur les tableaux et les membres de structure, les indirections et les prises d'adresses. (Toutefois, tous les opérandes sont autorisés dans le cas d'un sizeof.) Les expressions constantes des initialisateurs sont moins restreintes ; leurs opérandes peuvent être des constantes de tout type, et l'on peut y appliquer l'opérateur & unaire à des objets extemes ou statiques, ainsi qu'à des tableaux externes ou statiques indexés par une expression constante. On peut également appliquer l'opérateur & unaire implicitement, en écrivant des tableaux non indexés ou des fonctions. Les valeurs que peut prendre un initialisateur sont d'une part une constante, d'autre part l'adresse d'un objet exteme ou statique déclaré au préalable, plus ou moins une constante.

Les expressions constantes entières qui suivent un #if obéissent à des contraintes plus sévères : les expressions sizeof, les constantes énumérées et les conversions de types y sont interdites (cf §12.5).

8. Les déclarations

Les déclarations indiquent le sens à donner à chaque identificateur ; elles ne réservent pas nécessairement la place mémoire associée à l'identificateur concemé. Les déclarations qui réservent de la mémoire s'appellent des définitiont. Les déclarations sont de la forme -

déclaration :

 

spécificateur-de-déclaration liste-de-déclarateurs-init-opt

 

Les déclarateurs figurant dans la liste de déclarateurs contiennent les identificateurs à déclarer ; les spécificateurs de déclaration consistent en une séquence de spécificatettrs de classe de stockage et de type.

spécificateur-de-déclaration :

 

spécificateur-de-classe-de-stockage spécificateur-de-déclaration
spécificateur-de-type spécificateur-de-déclaration-
opt
qualificatif-de-type spécificateur-de-déclaration-opt

 

liste-de-déclarateurs-init :

déclarateur-init
liste-de-déclarateurs-init ,déclarateur-init

 

déclarateur-init :

déclarateur = initialisateur

 

Nous présenterons les déclarateurs plus loin (§8.5) ; ils contiennent les noms à déclarer. Une déclaration doit comporter au moins un déclarateur, ou bien son spécificaj teur de type doit déclarer une étiquette de structure ou d'union, ou les membres d'une énumération ; les déclarations vides sont interdites.

8.1 Les spécificateurs de classe de stockage

Les spécificatettrs de classe de stockage sont :

spécificateur-de-classe-de-stockage :

 

auto
register
static
extern
typedef

 

Le sens des différentes classes de stockage a été exposé au §4.

Les spécificateurs auto et register donnent aux objets déclarés la classe de stockage automatique, et on ne peut les employer qu'à l'intérieur des fonctions. De telles déclarations servent aussi de définitions et réservent de la mémoire. Une déclaration de classe register équivaut à une déclaration de classe auto, mais indique que les objets déclarés seront accédés fréquemment. Peu d'objets sont effectivement placés dans des registres, et seuls certains types sont autorisés ; ces restrictions dépendent de l'implémentation. Dans tous les cas, si un objet est déclaré de classe register, on ne peut pas lui appliquer l'opérateur & unaire, explicitement ou implicitement.

La rêgle selon laquelle il est interdit de calcu1er l'adresse d'un objet déclaré de classe register, mais étant en fait de classe auto, est nouvelle.

 

Le spécificateur static donne aux objets déclarés la classe de stockage statique, et on peut l'employer à l'intérieur ou à l'extérieur des fonctions. A l'intérieur d'une fonction, ce spécificateur réserve de la mémoire, et sert de définition ; son effet à l'extérieur d'une fonction est décrit au §11.2.

Une déclaration comportant le spécificateur extern, et figurant à l'intérieur d'une fonction, indique que l'espace mémoire réservé aux objets déclarés est défini ailleurs ; son effet à l'extérieur d'une fonction est décrit au §11.2.

Le spécificateur typedef ne réserve pas de mémoire, et c'est seulement par commodité syntaxique qu'il figure parmi les spécificateurs de classe de stockage ; il est décrit au §8.9.

On ne peut faire figurer qu'un seul spécificateur de classe de stockage par déclaration. Si l'on n'en donne pas, les règles suivantes s'appliquent : les objets déclarés à l'intérieur d'une fonction sont de classe auto ; les fonctions déclarées à l'intérieur d'une fonction sont de classe extern ; les objets et les fonctions déclarés à l'extérieur des fonctions sont de classe statique, avec lien externe (cf § §10-Al1).

8.2 Les spécificateurs de type

Les spécificateurs de type sont :

spécificateur-de-type :

 

void
char
short
int
long
float
double
signed
unsigned

spécificareur-de-struct-ou-union
spécificateur-d'énumération
nom-typedef

 

On peut ajouter l'un des mots short ou long à int ; le sens est le même si l'on omet int. On peut également ajouter le mot long à double. On peut ajouter l'un des mots signed ou unsigned à int, à ses variantes short ou long, ou bien à char. Si l'on n'écrit que signed ou unsigned, cela représente un int. Le spécificateur signed sert à forcer les objets de type char à porter un signe ; pour les autres types entiers, signed est autorisé, mais redondant.

Mis à part les cas ci-dessus, on ne peut faire figurer qu'un seul spécificateur de type par déclaration. Si l'on n'en donne pas, le type est int.

On peut aussi qualifier les types, afin de donner des propriétés particulières aux objets déclarés.

qualificatif-de-type:

 

const
volatile

 

On peut ajouter des qualificatifs de type à n'importe quel spécificateur de type. On peut initialiser un objet const, mais on ne peut rien lui affecter par la suite. Le sens d'un objet volatile dépend de l'implémentation.

Les propriétés const et volatile sont des nouveautés de la norme ANSI. const permet au compilateur de connaître les objets qui peuvent étrc placés en mémoire morte, et lui donne parfois de meilleures possibilités d'optimisation. Le qualificatif volatile sert à forcer une implémentation donnée à ne pas réaliser d'optimisation indésirable. Par exemple, dans le cas d'une machine dont les registres d'entrée-sorties se situent à des adresses de Ia mémoire centrale, on peut déclarer un pointeur sur un registre de périphérique comme pointeur sur volatile, afin que le compilateur ne supprime pas les indirections via ce pointcur, apparemment redondantes. les compilateurs peuvent ne pas tenir compte de ces qualificatifs, mis à part qu'ils doivent signaler les tentatives explicites de modification des objets const.

 

8.3 Les déclarations de structures et d'unions

Une structure est un objet composé d'une séquence de membres de types divers, portant des noms. Une union est un objet qui contient, selon les moments, l'un de ses membres, qui sont de types divers. Les spécificateurs de structures et d'unions sont de la même forme.

spécificateur-de-struct-ou-union :

 

struct-ou-union identificateur-opt { liste-de-déclarartions-de-struct}
struct-ou-union identificateur

 

struct-ou-union :

struct
union

 

Une liste-de-déclarations-de-struct est une séquence de déclarations des membres de la structure ou de l'union :

liste-de-déclarations-de-struct :

 

déclaration-de-struct
liste-de-déclarations-de-struct déclaration-de-struct

 

déclaration-de-struct :

liste-de-qualificatifs-de-spécificateurs liste-de-déclarateurs-de-struct ;

 

liste-de-qualificatifs-de-spécificateurs :

spécificateus-de-type liste-de-qualificatifs-de-spécificateurs-opt
qualificatif-de-type liste-de-qualificatifs-de-spécificateurs-opt

 

liste-de-déclarateurs-de-struct :

déclarareur-de-struct
liste-de-déclarateurs-de-struct
, déclarateur-de-struct

 

En général, un déclarateur-de-struct est simplement le déclarateur d'un membre de la structure ou de l'union. Cependant, un membre de structure peut aussi être constitué d'un nombre de bits donnés. Un tel membre s'appelle un champ de bits, ou un champ tout court ; sa longueur est séparée du déclarateur donnant son nom par un signe deux-points.

déclarareur-de-struct :

 

déclarareur
déclarareur-
opt : expression-constante

 

Un spécificateur de type de la forme

struct-ou-union identificateur-opt {liste-de-déclarations-de-struct }

 

déclare que l'identificateur est l'étiquette de la structure ou de l'union indiquée par la liste. Les déclarations ultérieures figurant dans les limites de la portée courante peuvent faire référence au même type en spécifiant cette étiquette, sans la liste associée :

struct-ou-union identificateur

 

Si un spécificateur ne comportant pas de liste figure avant que son étiquette ne soit définie, il en résulte un type incomplet. On peut se servir des objets de type structure ou union incomplet aux endroits où l'on a pas besoin de connaître leur taille, par exemple dans des déclarations (pas des définitions), pour spécifier un pointeur, ou pour créer un typedef, à l'exclusion de tous autres cas, Le type en question devient complet dès l'apparition d'un spécificateur portant la même étiquette et contenant une liste de déclarations. Même dans le cas des spécificateurs comportant une liste, le type structure ou union en cours de déclaration est incomplet à l'intérieur de la liste ; il ne devient complet qu'à partir du signe } qui termine le spécificateur.

Aucun membre d'une structure ne peut être de type incomplet. Par conséquent, il est impossible de déclarer une structure ou une union qui contienne une instance d'elle-même. Toutefois, en plus de baptiser le type structure ou union concemé, les étiquette permettent de définir des structures auto-référentes ; une structure ou une union peut en effet contenir un pointeur sur une instance d'elle-même, car on peut déclarer des pointeurs sur des types incomplets.

Une règle très particulière s'applique aux déclarations de la forme

struct-ou-union identificateur ;

 

qui déclarent une structure ou une union, mais ne comportent ni liste de déclarations, ni déclarateurs. Même si l'identificateur est une étiquette de structure ou d'union déjà déclarée et visible (§11.1), une telle déclaration fait de l'identificateur l'étiquette d'une nouvelle structure ou union, de type incomplet, pour la portée courante.

Cette règle obscure est une nouveauté de la norme ANSI. Elle sert à limiter les structures mutuellement récursives déclarées dans la portée courante, mais dont les étiquettes peuvent déjà avoir été déclarées dans une portée de niveau supérieur.

 

Un spécificateur de structure ou d'union comportant une liste, mais pas d'étiquette, crée un type unique, dont on ne peut se servir directement que dans la déclaration dont il fait partie.

Les noms de membres et d'étiquettes ne peuvent pas entrer en conflit entre eux, ni avec ceux des variables ordinaires. Un même nom de membre ne peut pas figurer deux fois dans la même structure ou union, mais il peut servir dans des structure ou des unions différentes.

Dans la première édition de ce livre, les noms des membre de structure et d'union n'étaient pas associés à leur parent. Cependant les compilateurs réalisaient couramment cette association bien avant la norme ANSI.

 

Les membre de structure ou d'union, à l'exception des champ de bits, peuvent être de n'importe quel type. Les champs de bits (qui n'ont pas obligatoirement de déclarateur, et peuvent donc ne pas porter de nom) sont de type int, unsigned int ou signed int, et sont interprétés comme des objets de type entier dont la longueur en bits est donnée ; le fait que les champs de type int soient considérés comme signés ou non dépend de l'implémentation. Les champs adjacents dans une structure sont regroupés dans des unités de mémoire dépendant de l'implémentation dans un sens dépendant de l'implémentation. Lorsqu'un champ suivant un autre champ ne rentre pas dans une unité de mémoire partiellement remplie, il peut être divisé entre deux unités, ou l'unité en question peut être complétée à vide. Un champ nom nommé de largeur 0 force ce remplissage complémentaire, afin que le champ suivant commence au début de l'unité d'allocation suivante.

La norme ANSI rend les champs encore plus dépendants de l'implémentation que ne le faisait la première édition. Il est conseillé de considérer que les régles du langage concernant la mémorisation des champs de bits dépendent entièrement de l'imp1émentation. On peur employer des structures comportant des champs de bits dans le but de réduire la taille mémoirc nécessaire à ces structures, de façon portable (et probablernent au prix d'une augmentation du nombre d'instructions et du temps d'exécution nécessaires pour accéder à ces champs), ou bien pour décrire une structure de données au niveau du bit de façon non portable. Dans ce cas, il est impératif de connaître les règles de l'implémentation utilisée.

 

Les membres d'une structure se situent à des adresses croissantes dans l'ordre de leurs déclarations, Les membres autres que des champs sont alignés sur une limite d'adresse qui dépend de leur type ; par conséquent, une structure peut contenir des vides non baptisés. Si l'on convertit le type d'un pointeur sur une structure en celui d'un pointeur sur son premier membre, le résultat pointe sur ce premier membre.

On peut considérer une union comme une structure dont tous les membres commencent à son début (déplacement 0), et de taille suffisante pour contenir n'importe lequel d'entre eux. Une union ne peut contenir qu'un seul de ses membres à un instant donné, Si l'on convertit le type d'un pointeur sur une union en celui d'un pointeur sur l'un de ses membres, le résultat pointe sur ce membre.

Voici un exemple simple de déclaration de structure :

struct noeud { 
        char mot [20];
        int nb;
        struct noeud *gauche;
        struct noeud *droite;
}; 

qui contient un tableau de 20 caractères, un entier et deux pointeurs sur des structures semblables. Après la déclaration de cette structure, la déclaration

struct noeud s, *ps;

 

déclare s comme une structure de cette forme et ps comme un pointeur sur une telle structure. Avec ces déclarations, l'expression

ps->nb

 

désigne le champ nb de la structure pointée par ps;

s.gauche

 

désigne le pointeur sur le sous-arbre gauche de la structure s ; et

s.droite->mot[0]

 

désigne le premier caractère du membre mot du sous-arbre droit de s.

En général, on ne peut pas lire un membre d'une union sans avoir affecté auparavant une valeur à cette union via ce même membre. Toutefois, il existe une règle particuliére qui simplifie l'usage des unions : si une union contient plusieurs structures ayant une partie initiale commune, et si cette union a pour valeur actuelle l'une de ces structures, on peut faire référence à la partie initiale commune de n'importe laquelle des structures en question. Par exemple, ceci est un fragment de programme valide :

union { 
    struct { 
        int type; 
    } n; 
    struct { 
        int type; 
        int noeud int; 
    } ni; 
    struct { 
        int type; 
        float noeud float; 
    } nf; 
} u; 
... 
u.nf.type = FLOAT; 
u.nf.noeud_float = 3.14; 
... 
if (u.n.type == FLOAT) 
... sin(u.nf.noeud_float) ... 

8.4 Les énumérations

Les énumérations sont des types uniques dont les valeurs possibles font partie d'un ensemble de constantes ponant un nom, que l'on appelle des énumérateurs. La forme d'un spécificateur d'énumération est similaire à celle des structures et des unions.

spécificateur-d'énumération :

 

enum identificateur-opt { liste-d'énumérateurs }
enum identificateur

 

liste-d'énumérateurs :

énumérateur
liste-d'énumérateurs , énumérateur

 

énumérateur:

identificateur = expression-constante

 

Les identificateurs faisant partie d'une liste d'énumérateurs sont déclarés comme des constantes de type int, et peuvent figurer partout où des constantes sont nécessaires. Si aucun des énumérateurs ne comporte un signe =, la première constante correspondante vaut 0 et les suivantes augmentent de 1 en 1 de gauche à droite. Un énumérateur componant un signe = donne à l'identificateur concerné la valeur indiquée ; les identificateurs suivants continuent la progression de 1 en 1 à partir de cette valeur.

Les noms d'énumérateurs de même portée doivent être tous différents, et distincts des noms de variables ordinaires ; cependant, leurs valeurs ne sont pas toutes obligatoirement distinctes.

L'identificateur d'un spécificateur d'énumération joue le même rôle que celui de l'étiquette d'une structure ; il donne un nom à une énumération particulière. Les règles concernant les spécificateurs d'énumérations comportant ou non une étiquette ou une liste sont les mêmes que dans le cas des spécificateurs de structures ou d'unions, mis à part qu'ù n'existe pas de types incomplets d'énumérations ; l'étiquette d'un spécificateur d'énumération ne comportant pas de liste d'énumérateurs doit se rapporter à un spécificateur visible comportant une liste.

Les énumétations n'existaient pas dans la premiére édition de ce 1ivre, mais elles font partie du langage depuis quelques années.

 

8.5 Les déclarateurs

La syntaxe des déclarateus est la suivante :

déclarateur :

 

pointeur-opt déclarateur-absolu

 

déclarateur-absolu :

identificateur
( déclarateur )
déclarateur-absolu [ expression-constante-opt ]
déclarateur-absolu ( liste-de-types-de-paramètres )
déclarateur-absolu ( liste-d'identificateurs-opt )

 

pointeur :

* liste-de-qualificatifs-de-type-opt
* liste-de-qualificatifs-de-type-opt pointeur

 

liste-de-qualificatifs-de-type:

qualificatif-de-type
liste-de-qualificatifs-de-type qualificatif-de-type

 

La structure des déclarateurs ressemble à celle des expressions d'indirection, de fonctions et de tableaux ; ils se groupent de la même manière.

8.6 La signification des déclarateurs

Une liste de déclarateurs figure après une séquence de spécificateurs de type et de classe de stockage. Chaque déclarateur déclare un identificateur principal unique, celui qui figure comme première possibilité de production pour le déctarateur-absolu. Le spécificateur de classe de stockage s'applique directement à cet identificateur, mais son type dépend de la forme de son déclarateur. Un déclarateur se lit comme une assertion énonçant que lorsque son identificateur figure dans une expression de même forme que le déclarateur, il donne un objet du type indiqué.

Si l'on se borne à examiner les parties des spécificateurs de déclaration concernant le type (§8.2) et un déclarateur particulier, une déclaration est de la forme "T D", où T est un type et D un déclarateur. Cette notation décrit de façon inductive le type attribué à l'identificateur selon la forme du déclarateur.

Dans une déclaration T D D est un simple identificateur, cet identificateur est de type T.

Dans une déclaration T D D est de la forme

( D1 )

 

l'identificateur figurant dans D1 est de même type que celui de D. Les parenthèses ne modifient pas le type, mais elles peuvent changer l'interprétation des déclarateurs complexes.

8.6.1 Les déclarateurs de pointeurs

Dans une déclaration T D D est de la forme

* liste-de-qualifitcatifs-de-type-opt D1

 

et où l'identificateur figurant dans la déclaration T D1 est de type "modificateur-de-type T", l'identificateur de D est de type "modificateur-de-type liste-de-qualificatifs-de-type pointeur sur T". Les qualificatifs qui suivent le signe * s'appliquent au poin-teur même, et non à l'objet sur lequel il pointe. '

Examinons par exemple la déclaration

int *tp[];

 

Ici, c'est tp[] qui joue le rôle de D1 ; une déclaration " int tp[] " (plus loin) donnerait à tp le type "tableau de int ", la liste de qualificatifs de type est vide et le modificateur de type est "tableau de". Par conséquent, cette déclaration donne à tp le type "tableau de pointeurs sur int ".

Pour prendre d'autres exemples, les déclarations

int i, *pi, *const cpi = &i;
const int ci = 3, *pci;

 

déclarent un entier i et un pointeur sur un entier pi. On ne peut pas modifier la valeur du pointeur constant cpi ; il pointera toujours sur le même emplacement, mais on pourra néanmoins modifier la valeur à laquelle il fait référence. L'entier ci est constant, on ne peut donc pas le modifier (bien que l'on puisse l'initialiser, comme ici). pci est de type "pointeur sur const int ", et l'on peut modifier pci lui-même afin de le faire pointer ailleurs, mais on ne peut pas modifier la valeur sur laquelle il pointe par une affectation via pci.

8.6.2 Les déclarateurs de tableaux

Dans une déclaration T DD est de la forme

D1[expression-constante-opt]

 

et où l'identificateur figurant dans la déclaration T D1 est de type "modificateur-de-type T", l'identificateur de D est de type "modificateur-de-type tableau de T". Si une expression constante est présente, elle doit être de type entier, et de valeur supérieure à 0. Si cette expression constante est absente, le tableau est de type incomplet.

On peut construire un tableau à partir d'un type arithmétique, d'un pointeur, d'une structure ou d'une union, ou d'un autre tableau (ce qui donne un tableau à plusieurs dimensions). On ne peut construire un tableau qu'à partir de types complets ; on ne peut pas construire un tableau de structures de type incomplet. Ainsi, dans le cas d'un tableau à plusieurs dimensions, seule la première dimension peut être absente. Le type d'un objet de type tableau incomplet se complète par une autre déclaration, complète, de cet objet (§10.2), ou par une initialisation (§8.7). Par exemple,

float tf [17] , *tpf [17] ;

 

déclare un tableau de nombres flottants et un tableau de pointeurs sur des nombres flottants. Et

static int x3d[3][5][7];

 

déclare un tableau d'entiers statique à trois dimensions, de taille 3x5x7. Plus exactement, x3d est un tableau de trois éléments, dont chacun est un tableau de cinq tableaux ; chacun de ces derniers tableaux contient sept entiers. Les expressions x3d, x3d[i] , x3d[i][j] et x3d[i][j][k] sont toutes valides. Les trois premières sont de type "tableau", la demière de type int. Plus précisément, x3d[i][j] est un tableau de 7 entiers, et x3d[i] est un tableau de 5 tableaux de 7 entiers.

L'opération d'indexation des tableaux est définie de façon, que E1[E2] soit identique à *(E1+E2). Par conséquent, malgré son aspect asymétrique, l'indexation est une opération commutative. A cause des règles de conversion qui s'appliquent à l'opérateur + et aux tableaux (§§6.6, 7.1, 7.7), si E1 est un tableau et E2 un entier, E1[E2] désigne l'élément de E1 d'indice E2.

Dans l'exemple ci-dessus, x3d[i][j][k] équivaut à *(x3d[i][j]+k) . La première sous-expression x3d[i][j] est convertie dans le type "pointeur sur un tableau d'entiers", d'après le §7.1 ; et d'après le §7.7, l'addition s'effectue après avoir multiplié k par la taille d'un entier. Ces règles impliquent que les tableaux sont, mémorisés par rangées (c'est le demier indice qui varie le plus vite), et que le premier indice figurant dans la déclaration permet de déterminer la place mémoire occupée par un tableau, mais ne joue aucun autre rôle dans les calculs d'indices.

8.6.3 Les déclarateurs de fonctions

Dans une déclaration de fonction sous la nouvelle forme T DD est de la forme

D1 ( liste-de-types-de-paramètres )

 

et où l'identificateur figurant dans la déclaration T D1 est de type "modificateur-de-type T", l'identificateur de D est de type "modificateur-de-type fonction d'arguments liste-de-types-de-paramètres retournant T".

La syntaxe des paramètres est la suivante :

liste-de-types-de-paramètres :

 

liste-de-paramètres
liste-de-paramètres
, ...

 

liste-de-paramètres :

déclaration-de-paramètres
liste-de-paramètres
, déclaration-de-paramètre

 

déclaration-de-paramètres:

spécificateurs-de-déclaration déclarateur
spécificateur-de-déclaration déclarateur-abstrait
-opt

 

Dans la nouvelle forme des déclarations, la liste de paramètres indique les types des paramètres. Dans le cas particulier d'un déclarateur de fonction sous la nouvelle forme ne comportant pas de paramètres, la liste de types de paramètres est constituée du seul mot-clé void. Si la liste de types de paramètres se termine par une ellipse ",...", la fonction peut accepter plus d'arguments que le nombre de paramètres décrits explicitement (cf. §7.3.2).

Les paramètres de type tableau ou fonction sont transformés en pointeurs, en accord avec les règles de conversion des paramètres (cf. §10,1). Le seul spécificateur de classe de stockage autorisé dans un spécificateur de déclaration de paramètres est register, et il n'est pris en compte que si le déclarateur de fonction est suivi d'une définition de fonction. De même, si les déclarateurs figurant dans les déclarations de paramètres comportent des identificateurs et si le déclarateur de fonction n'est pas suivi d'une définition de fonction, ces identificateurs deviennent immédiatement invisibles. Les déclarateurs abstraits, qui ne précisent pas les identificateurs, sont présentés au §8.8.

Dans une déclaration de fonction sous l'ancienne forme T DD est de la forme

D1 ( liste-d'identificateurs-opt )

 

et où l'identificateur figurant dans la déclaration T D1 est de type "modificateur-de-type T", l'identificateur de D est de type "modificateur-de-type fonction d'arguments non spécifiés retournant T". Les paramètres éventuels sont de la forme

liste-d'identificateurs :

 

identificateur liste-d'identificateurs , identificateur

 

Dans un déclarateur sous l'ancienne forme, la liste d'identificateurs ne doit figurer que si ce déclarateur est placé en tête d'une définition de fonction (§10.1). La déclaration ne fournit aucun renseignement sur les types des paramètres.

Par exemple, la déclarationint

f(), *fpi(), (*pfi)();

 

déclare une fonction f retournant un entier, une fonction fpi retournant un pointeur sur un entier, et un pointeur pfi pointant sur une fonction retournant un entier. Aucune de ces déclarations ne comporte les types des paramètres ; elles sont sous l'ancienne forme.

D'après la déclaration sous la nouvelle forme

int strcpy (char *dest, const char *source), rand(void) ;

 

strcpy est une fonction retournant un int qui accepte deux arguments, le premier étant un pointeur de caractère et le second un pointeur sur une chaîne de caractères constante. Les noms des paramètres servent en fait de commentaires. La deuxième fonction, rand, ne prend pas d'arguments et retourne un int.

Les déclarateurs de fonction comportant des prototypes de paramètres constituent de loin la plus grande modification apportée au langage par la norme ANSI. Leur avantage par rapport aux "anciens" déclarateurs de la première édition est qu'ils permettent de détecter des erreurs et de forcer les types des arguments pour les appels de fonctions, mais c'est au prix de bouleversements et de confusions lors de leur introduction, et de la nécessité de pouvoir traiter les deux formes. Pour assurer cette compatibilité, certaines syntaxes sont peu heureuses, en particulier le void servant à marquer explicitement les fonctions sous la nouvelle forme ne comportant pas de paramètres.
La notation d'ellipse ",..." désignant les fonctions à liste variable d'arguments est également une nouveauté, et elle formalise, avec les macros définies dans l'en-t6te standard , un mécanisme qui était officiellement interdit mais officieusement toléré dans la première édition.
Ces notations sont inspirées du langage C++.

 

8.7 L'initialisation

Au moment de la déclaration d'un objet, son déclarateur-init peut spécifier une valeur initiale pour l'identificateur déclaré. L'initialisateur est précédé de =, et il est constitué soit d'une expression, soit d'une liste d'initialisateurs imbriqués entre accolades. Une telle liste peut se terminer par une virgule, pour de simples questions d'esthétique.

initialisateur :

 

expression-d'affectation
{ liste-d'initialisateurs }
{
liste-d'initialisateurs , }

 

liste-d'initialisateurs :

initialisateur
liste-d'initialisateurs
, initialisateur

 

Toutes les expressions figurant dans l'initialisateur d'un objet ou d'un tableau statique doivent être des expressions constantes, décrites au §7,19. Les expressions figurant dans l'initialisateur d'un objet ou d'un tableau de classe auto ou register doivent aussi être des expressions constantes si cet initialisateur est une liste entre accolades. Toutefois, si l'initialisateur d'un objet automatique est constitué d'une seule expression, celle-ci n'est pas obligatoirement une expression constante, elle doit seulement être de type convenable pour l'affectation à cet objet.

La première édition n'admettait pas que l'on initialise des structures, des unions ou des tableaux automatiques. La norme ANSI autorise ces initialisations, mais uniquement avec des constructions constantes, sauf si l'on peut exprimer l'initialisateur grâce à une seule expression.

 

Un objet statique qui n'est pas initialisé explicitement prend pour valeur initiale la constante 0. Dans le cas d'un objet statique composé, tous ses éléments sont initialisés à 0. La valeur initiale d'un objet automatique non initialisé n'est pas définie.

Initialisateur d'un pointeur ou d'un objet de type arithmétique est constitué d'une seule expression, éventuellement entre accolades. Cette expression est affectée à l'objet en question.

Initialisateur d'une structure est soit une expression du même type, soit une liste initialisateur placée entre accolades, correspondant à chacun de ses membres, dans l'ordre. Si cette liste comporte moins d'initialisateur que le nombre de membres de la structure, les derniers membres prennent la valeur initiale 0. Il est interdit de donner plus d'initialisateurs qu'il n'y a de membres.

Initialisateur d'un tableau est une liste d'initialisateurs placée entre accolades, correspondant à chacun de ses membres. Si la taille du tableau n'est pas précisée, elle est déterminée par le nombre d'initialisateurs, et le type du tableau devient complet. Si la taille du tableau est fixée, le nombre d'initialisateurs ne doit pas dépasser le nombre d'éléments du tableau ; si l'on en donne moins, les derniers éléments prennent la valeur initiale 0.

Il existe un cas particulier : on peut initialiser un tableau de caractères par une constante de type chaîne ; les caractères successifs de la chaîne initialisent un à un les éléments du tableau. De même, une constante de type chaîne de caractères étendus (§2.6) peut initialiser un tableau de type wchar_t. Si la taille du tableau n'est pas précisée, elle est déterminée par le nombre de caractères de la chaîne, y compris le \0 final ; si la taille du tableau est fixée, le nombre de caractères de la chaîne, non compris le \0 final, ne doit pas dépasser la taille du tableau.

Initialisateur d'une union peut être constitué soit d'une expression unique de même type, soit d'un initialisateur du type du premier membre de l'union, placé entre accolades.

La première édition interdisait l'initialisation des unions. La règle du "premier membre" est maladroite, mais il est difficile de la généraliser sans introduire une nouvelle syntaxe. A côté du fait qu'elle autorise l'initialisation explicite des unions, du moins sorts une forme primitive, cette règle de l'ANSI définit la sémantique des unions statiques non initialisées.

 

Un agrégat est une structure ou un tableau. Si un agrégat comporte des éléments de type agrégat, les règles d'initialisation s'appliquent récursivement. On peut omettre des accolades dans l'initialisation d'après les règles suivantes : si l'initialisateur d'un élément d'agrégat étant lui-même un agrégat commence par une accolade ouvrante, alors la liste suivante d'initialisateurs séparés par des virgules initialise les éléments du sous-agrégat ; cette liste ne doit pas comporter plus d'initialisateurs que le nombre d'éléments du sous-agrégat. Cependant, si l'initialisateur d'un sous-agrégat ne commence pas par une accolade ouvrante, alors on utilise juste assez d'éléments de la liste pour initialiser le sous-agrégat ; les éléments restants éventuels servent à initialiser l'élément suivant de l'agrégat dont fait partie le sous-agrégat.

Par exemple,

int x[] = ( 1, 3, 5 );

 

déclare et initialise x comme un tableau à une dimension comportant trois éléments, car sa taille n'est pas précise et il y a trois initialisateurs.

float y[4][3] = { 
    { 1, 3, 5 ), 
    { 2, 4, 6 ), 
    { 3, 5, 7 ), 
}; 

est une initialisation où figurent toutes les accolades : 1, 3 et 5 initialisent la première rangée du tableau y[0] , c'est-à-dire y[0][0] , y[0][1] et y[0][2]. De même, les deux lignes suivantes initialisent y[1] et y[2]. L'initialisateur s'arrête à ce moment, si bien que les éléments de y[3] sont initialisés à 0. On aurait obtenu exactement le même résultat en écrivant

f1oat y[4][3] = { 
    1, 3, 5, 2, 4, 6, 3, 5, 7 
}; 

L'initialisateur de y commence par une accolade ouvrante, mais pas celui de y[0] ; par conséquent, y[0] est initialisé par les trois premiers éléments de la liste. De même, les trois suivants servent à initialiser y[1] , et les trois derniers y[2] . Quant à la ligne

float y[4][3] = { 
    { 1 } , { 2 }, { 3 }, { 4 } 
}; 

elle initialise la première colonne de y (considéré comme un tableau à deux dimensions) et met le reste à 0.

Enfin,

char msg[] = "Erreur de syntaxe en ligne %s\n";

 

est un exemple de tableau de caractères dont les éléments sont initialisés par une chaîne ; sa taille est calculée en incluant le caractère \0 final.

8.8 Les noms de types

Dans divers contextes (pour réaliser des conversions de types explicites par des "casts", pour déclarer les types des paramètres dans les déclarateurs de fonction, et pour l'opérande de sizeof), il faut indiquer le nom d'un type de données. Cela se réalise grâce à un nom de type, dont la syntaxe est celle d'une déclaration d'objet de ce type où l'on omet le nom de l'objet.

nom-de-type :

 

liste-de-qualificatifs-de-spécifcateurs déclarateur-abstrait-opt

 

déclarateur-abstrait:

pointeur pointeur-opt
déclarateur-abstrait-absolu

 

déclarateur-abstrait-absolu :

( déclarateur-abstrait )
déclarateur-abstrait-absolu-opt [ expression-constante-opt ]
déclarateur-abstrait-absolu-opt ( liste-de-types-de-paramètres-opt )

 

On peut se contenter d'indiquer l'endroit du déclarateur abstrait où figurerait l'identificateur si cette construction était le déclarateur d'une déclaration. Le nom de type ainsi obtenu représente le type de cet identificateur hypothétique. Par exemple,

int
int *
int *[3]
int (*)[]
int *()
int (*[])(void)

 

sont respectivement les noms des types "entier", "pointeur sur un entier", "tableau de 3 pointeurs sur des entiers", "pointeur sur un tableau d'entiers de taille indéfinie", "fonction de paramètres indéfinis retournant un pointeur sur un entier" et "tableau, de taille indéfinie, de pointeur sur des fonctions sans paramètres, retournant chacune un entier".

8.9 Typedef

Les déclarations dont le spécificateur de classe de stockage est typedef ne déclarent pas des objets, mais définissent des identificateurs représentant des types. Ces identificateurs sont appelés des noms typedef.

nom-typedef :

 

identificateur

 

Une déclaration de classe typedef attribue un type à chacun des noms de ses déclarateurs, de la façon habituelle (cf. §8.6). Par la suite, chacun de ces noms typedef équivaut syntaxiquement à un spécificateur du type qui lui est associé.

Par exemple, après

typedef long Numbloc, *Ptrbloc;
typedef struct ( double r, theta; ) Complexe;

 

les constructions

Numbloc b;
extern Ptrbloc pb;
Complexe z, *pz;

 

sont des déclarations correctes. b est de type long, pb de type "pointeur sur long", le type de z est la structure indiquée et pz est un pointeur sur une telle structure.

typedef n'introduit pas de nouveaux types, mais des synonymes pour des types que l'on pourrait spécifier autrement. Dans notre exemple, b est de même type que tout autre objet de type long.

On peut redéclarer des noms typedef dans une portée plus restreinte, mais il faut alors donner un ensemble non vide de spécificateurs de type. Par exemple,

extern Numbloc;

 

ne redéclare pas Numbloc, alors que

extern int Numbloc;

 

le redéclare.

8.10 Les équivalences de types

Deux listes de spécificateurs de type sont équivalentes si elles contiennent les mêmes spécificateurs, en tenant compte du fait que certains spécificateurs en impliquent d'autres (par exemple, long tout court signifie long int). Les structures, les unions et les énumérations dont les étiquettes différent sont distinctes, et une structure, une union ou une énumération sans étiquette spécifie un type unique.

Deux types sont identiques si leurs déclarateurs abstraits (§8.8), après développement des types typedef éventuels et suppression des identificateurs des paramètres de fonctions, sont les mêmes, y compris leurs listes de spécificateurs de type. Les tailles des tableaux et les types des paramètres de fonctions sont significatifs.

9. Les instructions

A l'exception de certains cas particuliers, les instructions sont exécutées les unes à la suite des autres. Les instructions sont exécutées pour les effets qu'elles produisent et n'ont pas de valeurs. On les classe en plusieurs groupes.

instruction :

 

instruction-étiquetée
instruction-expression
instruction-composée
instruction-de-sélection
instruction-d'itération
instruction-de-saut

 

9.1 Les instructions étiquetées

Les instructions peuvent être précédées d'une étiquette.

instruction-étiquetée :

 

identigicateur : instruction
case expression-constante : instruction
default : instruction

 

Une étiquette constituée d'un identificateur déclare cet identificateur. Le seul usage possible d'une étiquette composée d'un identificateur est de servir de référence à un goto. La portée de l'identificateur est limitée à la fonction courante. Puisque les étiquettes possèdent leur propre classe de noms, elles ne peuvent interférer avec les autres identificateurs et ne peuvent pas être redéclarées. Voir §1 1.1.

On utilise les étiquettes de type case et default avec l'instruction switch (§ A9.4). L'expression constante employée avec case doit être de type entier.

Les étiquettes ne modifient pas en elles-mêmes le déroulement d'un programme.

9.2 Les instructions-expressions

La plupart des instructions sont des instructions-expressions de la forme

instruction-expression :

 

expression-opt ;

 

La plupart d'entre elles sont des affectations ou des appels de fonctions. Tous les effets de bord de l'expression sont pris en compte avant que l'instruction suivante ne soit exécutée. S'il n'y pas d'expression, on appelle cette construction une instruction nulle ; on utilise souvent une telle construction pour fournir un corps vide à une instruction de bouclage ou pour placer une étiquette.

9.3 Les instructions composées

Pour pouvoir mettre plusieurs instructions là où une seule est attendue, on peut utiliser une instruction composée (appelée également "bloc"). Le corps de la définition d'une fonction est une instruction composée.

instruction-composée :

 

{ liste-de-déclarations-opt liste-d'instructions-opt )

 

liste-de-déclarations:

déclaration
liste-de-déclarations déclaration

 

liste-d'instructions :

instruction
liste-d'instructions instruction

 

Si un identificateur de la liste des déclarations est déjà visible à l'extérieur du bloc, la déclaration extérieure est suspendue à l'intérieur du bloc (voir §11.1), après lequel elle sera de nouveau visible. On ne peut déclarer qu'une seule fois un identificateur dans le même bloc. Les règles sappliquent aux identificateurs d'une même classe de noms (§ 11); on traite de façon distincte les identificateurs des différentes classes de noms.

L'initialisation des objets dynamiques est effectuée à chaque fois qu'on entre dans le bloc par sa partie supérieure et dans l'ordre des déclarations. Si l'on exécute un saut à l'intérieur du bloc, on n'effectue pas ces initialisations. Les initialisations des objets static ne sont réalisées qu'une seule fois avant que le programme ne commence son exécution.

9.4 Les instructions de sélection

Les instructions de sélection permettent d'effectuer des choix entre différentes parties d'un programme.

instruction-de-sélection :

 

if ( expression ) instruction
if ( expression ) instruction e1se instruction
switch ( expression ) instruction

 

Dans les deux formes de l'instruction if, l'expression qui doit être de type arithmétique ou pointeur, est évaluée, y compris les effets de bord, et si le résultat est différent de 0, on exécute la première sous-instruction. Dans la seconde forme, on exécute la seconde sous-instruction si l'expression est égale à 0. L'ambiguïté de e1se est résolue en reliant un else à la dernière instruction if n'ayant pas de else correspondant et se trouvant au même niveau d'imbrication de bloc.

L'instruction switch permet de diriger le programme vers une instruction choisie parmi plusieurs, suivant la valeur d'une expression qui doit être de type entier. La sous-instruction contrôlée par un switch est généralement composée. Chaque instruction à l'intérieur de la sous-instruction peut être précédée d'une ou plusieurs étiquettes case (§9.1). L'expression de contrôle est transformée en valeur entière, et les constantes employées pour les différents cas le sont également. Il ne faut pas que deux constantes correspondant à deux cas différents et associées au même switch aient la même valeur après conversion. Egalement, il ne peut y avoir au plus qu'une seule étiquette default dans un switch. On peut imbriquer les instructions switch ; une étiquette case ou default est associée avec le plus petit switch qui la contient.

Quand l'instruction switch est exécutée, son expression est évaluée y compris tous les effets de bord et comparée avec les constantes associées à chaque cas. Si l'une des constantes est égal à la valeur de l'expression, le programme exécute l'instruction de l'étiquette case correspondante. Si aucune des constantes n'est égale à l'expression et s'il y a une étiquette default le programme exécute l'instruction correspondante. Si aucune constante ne convient et s'il n'y a pas d'étiquette default, alors aucune sous-instruction de l'aiguillage n'est exécutée.

Dans la première édition de ce livre, l'expression de contrôle de switch et les constantes associées aux différents cas devaient être de type int.

 

9.5 Les instructions d'itération

Les instructions d'itération spécifient le rebouclage.

instruction-d'itération :

 

while ( expression ) instruction
do instruction while ( expression ) ;
for ( expression-opt ; expression-opt ; expression-opt ) instruction

 

Dans les instructions while et for, la sous-instruction est exécutée de manière répétitive tant que la valeur de l'expression est différente de 0 ; l'expression doit être de type arithmétique ou de type pointeur. En ce qui concerne while, le test avec tous les effets de bord engendrés par l'expression est effectué avant chaque exécution de l'instruction ; pour do, le test est réalisé à la fin de l'itération.

Dans l'instruction for, la première expression n'est évaluée qu'une fois, et spécifie donc l'initialisation de la boucle. Il n'y a aucune restriction sur son type. La seconde expression doit être de type arithmétique ou de type pointeur ; elle est évaluée avant chaque itération et, si elle devient égale à 0, le for se termine. La troisième expression est évaluée après chaque itération et spécifie donc une ré-initialisation de la boucle. Il n'y a aucune restriction sur son type. Les effets de bord de chaque expression sont immédiatement pris en compte après leur évaluation. Si la sous-instruction ne contient pas continue, l'instruction ,

for ( expression1 ; expression2 ; expression3 ) instruction

 

est équivalente à

expression1 ; 
while ( expression2 ) { 
        instruction 
expression3 ; 
} 

On peut omettre n'importe laquelle des trois expressions. Si la seconde expression est absente, le test implicite équivaut à tester une constante non nulle.

9.6 Les instructions de saut

Les instructions de saut modifient le déroulement du programme sans conditions.

instruction-de-saut :

 

goto identificateur ;
continue ;
break ;
return
expression-opt ;

 

Dans l'instruction goto, l'identificateur doit être une étiquette (§9,1) située dans la fonction courante. L'exécution continue à l'instruction qui suit cette étiquette. On ne peut trouver une instruction continue qu'à l'intérieur d'une instruction de bouclage, Cette instruction dirige le programme vers la fin de la plus petite boucle qui l'entoure, Plus précisément, dans chacune des instructions ci-dessous :

while ( ... ) { 
    ... 
suite: ; 
}
do { 
    ... 
suite: ; 
} while ( ... );
for ( ... ) { 
    ... 
suite: ; 
} 

un continue qui ne fait pas partie d'une instruction d'itération imbriquée équivaut à un goto suite.

On ne peut trouver l'instruction break que dans une instruction de bouclage ou une instruction switch ; break termine l'exécution de la plus petite des instructions qui l'entourent. L'exécution reprend à l'instruction qui suit l'instruction terminée.

Une fonction retourne au programme appelant à l'aide de l'instruction return. Quand return est suivie d'une expression, la valeur de celle-ci est retournée au programme appelant. L'expression est convertie, de la même façon que pour une affectation, dans le type retourné par la fonction dans laquelle elle se trouve.

Omettre la fin d'une fonction est équivalent à un retour sans expression. Dans un tel cas, la valeur de retour est indéterminée.

10. Les déclarations externes

L'unité d'entrée fournie au compilateur C s'appelle une unité de traduction ; elle consiste en une suite de déclarations externes qui sont soit des déclarations, soit des définitions de fonctions.

unité-de-traduction :

 

déclaration-externe
unité-de-traduction déclaration-externe

 

déclaration-externe :

définition-de-fonction
déclaration

 

La portée des déclarations externes s'étend jusqu'à la fin de l'unité de traduction dans laquelle elles sont déclarées, exactement de la même façon que l'effet des déclarations internes d'un bloc s'étend jusqu'à la fin du bloc. La syntaxe des déclarations externes est la même que celle des autres déclarations, mis à part qu'on ne peut donner le code des fonctions qu'à ce niveau.

10.1 Les définitions de fonctions

Les définitions de fonctions ont la forme suivante

définition-de-fonction :

 

spécificateur-de-déclaration-opt déclarateur liste-de-déclarations-opt instruction-composée

 

Les seuls spécificateurs de classe de stockage autorisés sont extern et static ; voir §11.2 pour les différencier.

Une fonction peut retourner un type arithmétique, une structure, une union, un pointeur ou void, mais pas une fonction ou un tableau. Le déclarateur d'une déclaration de fonction doit indiquer explicitement que le spécificateur déclaré est de type fonction ; c'est-à-dire qu'il doit contenir l'une des formes suivantes (voir § 8.6.3)

déclarateur-absolu ( liste-de-paramétres-avec-types )
déclarateur-absolu ( liste-d'identtificateurs-opt )

 

où le déclarateur-absolu est un identificateur ou un identificateur parenthésé. En particulier, il ne doit pas acquérir le type fonction à l'aide d'un typedef.

Sous la première forme, la définition est une fonction d'un nouveau style et ses paramètres, accompagnés de leur type, sont déclarés dans la liste de paramètres avec types ; la liste de déclarations qui suit le déclarateur doit être absente. A moins que la liste de paramètres avec types ne contienne que void indiquant que la fonction n'a pas de paramètres, chaque déclarateur dans la liste de paramètres avec types doit contenir un identificateur. Si la liste de paramètres avec types se termine par ",...", alors la fonction peut être appelée avec plus d'arguments que de paramètres ; pour faire référence aux arguments supplémentaires, on doit utiliser le mécanisme de la macro va_arg défini dans le fichier d'en-tête et décrit à l'annexe B. Les fonctions utilisant une telle liste variable de paramètres doit avoir au moins un paramètre nommé.

Dans la seconde forme, la définition suit l'ancien style : la liste d'identificateurs donnent le nom des paramètres, pendant que la liste des déclarations donnent leur type. Si aucune déclaration n'est donnée pour un paramètre, son type est supposé être int. La liste de déclarations doit déclarer uniquement les paramètres indiqués dans la liste, il est impossible de les initialiser et register est le seul spécificateur de classe de stockage possible.

Dans les deux styles de définition de fonction, les paramètres sont supposés avoir été déclarés juste après le début de l'instruction composée qui forme le corps de la fonction et, par conséquent, les mêmes identificateurs ne doivent pas être redéclarés ici (bien qu'ils pourraient être, comme les autres identificateurs, redéclarés dans des blocs internes). si on déclare un paramètre de type "tableau de type", la déclaration est transformée en "pointeur de type" ; de même, si on déclare un paramètre de type "fonction retournant un type", la déclaration est transformée en "pointeur de fonction retournant un type". A l'appel d'une fonction, les arguments sont convertis si nécessaire et affectés aux paramètres ; voir §7.3.2.

Les définitions de fonctions sous la nouvelle forme sont une nouveauté de la norme ANSI. Il y a également un léger changement dans les détails de la promotion ; la première édition indiquait que les déclarations de paramètres float étaient transformés en double. La différence devient digne d'intérêt lorsqu'un pointeur sur un paramètre est généré à l'intérieur d'une fonction.

Voici un exemple complet de définition de fonction sous la nouvelle formeint

max(int a, int b, int c) 
{ 
    int m; 
    m = (a > b) ? a : b; 
    return (m > c) ? m : c; 
} 

Ici, la déclaration du spécificateur est int ; le déclarateur de la fonction est max(int a; int b, int c) et le bloc donnant le code de la fonction est {...}. La définition correspondante sous l'ancienne forme serait

int max(a, b, c) 
int a, b, c; 
{ 
    /* ... */ 
} 

où maintenant, le déclarateur est int max(a, b, c) et la liste de déclarations des paramètres est int a, b, c ;.

10.2 Les déclarations externes

Les déclarations externes indiquent les caractéristiques d'objets, de fonctions et d'autres identificateurs. Le terme "externe" fait référence à leur emplacement en dehors des fonctions et n'est pas directement lié au mot-clé extern ; la classe de stockage d'un objet défini de façon externe doit être laissée vide ou elle peut être spécifiée extern ou static.

Il peut exister plusieurs déclarations externes du même identificateur dans la même unité de traduction si elles correspondent au niveau du type et de l'édition de lien et s'il y a au plus une définition de l'identificateur.

Deux déclarations d'un objet ou d'une fonction sont supposées correspondre au niveau du type d'après les règles présentées au §8,10. De plus, si les déclarations différent parce qu'un type est une structure ou une union incomplète, ou un type énuméré incomplet (§8.3) et que l'autre est le type complet correspondant avec la même étiquette, on considère que les types correspondent. D'ailleurs, si la seule différence entre deux types est que l'un d'entre eux est un tableau complet et l'autre un tableau incomplet (§8.6.2), ils correspondent. Enfin, si un type spécifie une fonction sous l'ancienne forme et un autre spécifie une fonction sous la nouvelle forme, identique aux déclarations de paramètres près, ces types correspondent aussi.

Si la première déclaration externe d'une fonction ou d'un objet comprend le spécificateur static, l'identificateur a un lien interne ; sinon il a un lien externe. L'édition des liens est présentée au §11.2.

Une déclaration externe d'un objet est une définition si elle comporte un initialisateur. La déclaration d'un objet externe qui ne comporte pas d'initialisateur et qui ne contient pas le spécificateur extern est une définition potentielle. Si une unité de traduction comprend la définition d'un objet, toute définition potentielle est considérée comme une déclaration redondante. Si l'unité de traduction ne comprend aucune définition de l'objet, toute définition potentielle devient une définition unique avec une valeur initiale nulle.

Chaque objet doit avoir une et une seule définition. Pour les objets avec lien interne, cette règle s'applique séparément à chaque unité de traduction, parce que les objets avec lien interne sont uniques dans une unité de traduction donnée. Pour les objets avec lien externe, elle s'applique au programme entier.

Bien que la règle de la définition unique soit formulée de façon quelque peu différente dans la première version du livre, elle est en fait identique à celle énoncée ici. Certaines implémentations l'assouplissent en généralisant la notion de déclaration potentielle. Dans la formulation de remplacement qui est habituelle sur les systèmes UNIX et qui est reconnue comme l'extension normale par la norme, toutes les définitions potentielles d'un objet avec lien externe, tout au long des unités de traduction d'un programme, sont considérées ensemble et non séparément dans chaque unités de traduction. Si un programme comprend quelque part la définition d'une fonction, alors ses définitions potentielles deviennent simplement des déclarations, mais s'il ne comprend pas sa définition, alors toutes ses définitions potentielles deviennent des définitions initialisées à zéro.

 

11. La portée et l'édition de liens

Il n'est pas nécessaire de compiler entièrement un programme en une fois : le texte source peut être conservé dans plusieurs fichiers contenant des unités de traduction et on peut charger en provenance de bibliothèques des sous-programmes précompilés. On peut réaliser les relations entre les fonctions d'un programme à la fois au moyen d'appels explicites et par la manipulation de données externes.

Par conséquent, il existe deux sortes de portée à considérer : premièrement, la portée lexicale d'un identificateur qui est la région du texte du programme à l'intérieur de laquelle les caractéristiques de l'identificateur sont connues ; et deuxièmement, la portée associée aux objets et fonctions avec lien externe, qui détermine les rapports entre les identificateurs des différentes unités de traduction compilées séparément.

11.1 La portée lexicale

Les identificateurs sont répartis en plusieurs classes de noms qui n'interfèrent pas les unes avec les autres ; on peut utiliser le même identificateur dans des buts différents, même dans la même portée, on l'utilise dans des classes de noms différentes. Ces classes sont : les objets, les fonctions, les noms de typedef et les constantes énumérées ; les étiquettes ; les étiquettes de structures, d'unions et d'énumérations ; et les membres de chaque structure et d'union prises individuellement.

Ces règles sont différentes à plusieurs titres de celles décrites dans la première édition de ce manuel. Auparavant, les étiquettes ne possédaient pas leur propre classe ; les étiquettes de structures et d'unions avaient chacune une classe séparée, et dans certaines implémentations, les étiquettes d'énumérations en avaient une également ; le fait de mettre différentes sortes d'étiquettes dans la même classe de noms est une restriction nouvelle. Le plus important changement par rapport à 1a première édition est que chaque structure ou union crée une classe de nom séparée pour ses membres, si bien que l'on peut trouver le même nom dans plusieurs structures différentes. Cette règle est employé couramment depuis plusieurs années.

 

La portée lexicale de l'identificateur d'un objet ou d'une fonction dans une déclaration externe commence à la fin de sa déclaration et se poursuit jusqu'à la fin de l'unité de traduction dans laquelle elle se trouve. La portée d'un paramètre d'une définition de fonction commence au début du bloc qui définit la fonction et se termine à la fin de cette fonction ; la portée d'un paramètre dans une déclaration de fonction se termine à la fin de la déclaration. La portée d'un identificateur déclaré en tête d'un bloc commence à la fin de sa déclaration et se termine à la fin du bloc. La portée d'une étiquette est la totalité de la fonction dans laquelle elle apparaît. La portée d'une étiquette de structure, d'union ou d'énumération, ou celle d'une constante énumnérée, commence à l'endroit où elle figure dans un spécificateur de type, et se termine à la fin de l'unité de traduction (pour les déclarations externes) ou à la fin du bloc (pour les déclarations à l'intérieur d'une fonction).

Si un identificateur est explicitement déclaré en tête d'un bloc, y compris si ce bloc constitue une fonction, toute déclaration de l'identificateur en dehors du bloc est suspendue jusqu'à la fin du bloc.

11.2 L'édition de liens

A l'intérieur d'une unité de traduction, toutes les déclarations d'un même identificateur d'objet ou de fonction avec lien interne fait référence à la même chose et l'objet ou la fonction est unique pour cette unité de traduction. Toutes les déclarations d'un même identificateur d'objet ou de fonction avec lien externe font référence à la même chose et l'objet ou la fonction est partagé par le pro gramme entier.

Comme nous l'avons présenté au §10.2, la première déclaration externe d'un identificateur donne un lien interne à l'identificateur si le spécificateur static est utilisé, sinon elle lui donne un lien externe. Si la déclaration d'un identificateur à l'intérieur d'un bloc ne comprend pas le spécificateur extern, alors l'identificateur n'est pas lié et est unique pour la fonction. Si elle comprend extern, et si une déclaration externe de l'identificateur est active dans la portée entourant le bloc, l'identificateur est alors lié de la même façon que la déclaration externe et fait référence au même objet ou à la même fonction ; mais si aucune déclaration externe n'est visible, son lien est externe.

12. Le préprocesseur

Un préprocesseur effectue des substitutions de macros, une compilation conditionnelle et l'inclusion de fichiers nommés. Les lignes qui commencent par #, qui peut être précédé par un espace blanc, concernent ce préprocesseur. La syntaxe de ces lignes est indépendante du reste du langage ; on peut les trouver n'importe où et elles ont un effet qui s'étend (de façon indépendante de la portée) jusqu'à la fin de l'unité de traduction. Les limites des lignes sont importantes ; chaque ligne est analysée individuellement (voir tout de même le § AI 2.2 qui montre comment relier des lignes). Pour le préprocesseur, un lexème est un élément lexical quelconque du langage ou une suite de caractères donnant un nom de fichier comme dans la directive #include (§12.4) : de plus, tout caractère qui n'est pas défini par ailleurs est considéré comme un lexème. Cependant, l'effet des caractères d'espace blanc autre que l'espace et la tabulation horizontale est indéfini dans les lignes du préprocesseur. Le préprocesseur lui-même fonctionne en plusieurs phases logiques successives, dont certaines peuvent être rassemblées dans le cas d'une implémentation particulière.

  1. D'abord, les séquences d'échappement à trois caractères décrites au §12,1 sont remplacées par leurs équivalents. Si l'environnement du système le demande, des caractères de fin de ligne sont insérés entre les lignes du fichier source.
    • Chaque occurrence du caractère backslash suivi d'un caractère de fin de ligne est supprimée, de façon à joindre les lignes (§12.2).
      • Le programme est coupé en lexèmes séparés par des caractères d'espacement ; les commentaires sont remplacés par un simple espace. Alors, les directives du préprocesseur sont exécutées et les macros (§ §12.3-A12.10) sont développées.
        • Les séquences d'échappement figurant dans les constantes de type caractère et de type chaîne (§§2.5.2-2.6) sont remplacées par leurs équivalents, les constantes de type chaîne adjacentes sont concaténées.
          • Le résultat est traduit, puis lié avec les autres programmes et bibliothèques en rassemblant les programmes et les données nécessaires et en reliant les fonctions externes et les références d'objets à leurs définitions.

12.1 Les séquences d'échappement à trois caractères

Le jeu de caractères des programmes sources C est composé de caractères définis par l'ISO (ISO 644-1983 Invariant Code Set). Pour permettre aux programmes d'être représentés dans le jeu de caractères réduit, on remplace toutes les occurrences des séquences de trois caractères suivantes par le caractère unique correspondant. Ce remplacement est effectué avant tout traitement.

??= #       ??( [       ??< { 
??/ \       ??) ]       ??> }
??' ^       ??! |       ??- -

Aucun autre remplacement n'est effectué.

Les séquences d'échappement à trois caractères sont des nouveautés de la norme ANSI.

12.2 Le raccordement des lignes

Les lignes se terminant par le caractère \ sont reliées en supprimant le \ et le caractère suivant de fin de ligne. Ceci est effectué avant la division en lexèmes.

12.3 La définition et l'expansion des macros

Une ligne de contrôle de la forme

# define identificateur suite-de-lexèmes

 

demande au préprocesseur de remplacer les instances suivantes de l'identificateur par la suite de lexèmes indiquée ; les espacements en tête et en queue autour de la suite de lexèmes sont supprimés. Un second #define pour le même identificateur constitue une erreur à moins que la seconde suite de lexèmes soit identique à la première, où tous les espacements de séparation sont supposés équivalents.

Une ligne de la forme

# define identificateur ( identificateur ,..., identificateur ) suite-de-lexèmes

 

où il n'y a pas d'espace entre le premier identificateur et la (, est une définition de macro avec paramètres donnés par la liste d'identificateurs. Comme avec la première forme, les espaces en tête et en queue entourant la suite de lexèmes sont supprimés, et la macro peut être redéfinie uniquement à l'aide d'une définition dans laquelle le nombre et l'orthographe des paramètres et la suite de lexèmes sont identiques.

Une ligne de contrôle de la forme

# undef identificateur

 

demande que la définition de l'identificateur soit oubliée. Appliquer #undef à un identificateur inconnu ne constitue pas une erreur.

Quand une macro est définie avec la seconde forme, les instances textuelles ultérieures de l'identificateur de la macro suivies de caractères d'espacement éventuels, puis d'une suite de lexèmes séparés par des virgules entre parenthèses, constituent des appels de la macro. Les arguments de l'appel sont les suites de lexèmes séparées par des virgules ; les virgules se trouvant entre guillemets ou protégées par des parenthèses imbriquées n'effectuent pas de séparation d'arguments. Pendant l'assemblage, les arguments ne sont pas développés. Le nombre d'arguments de l'appel doit correspondre au nombre de paramètres de la définition. Une fois que les arguments sont isolés, les caractères d'espacement en tête et en queue sont supprimés. Alors, la suite de lexèmes résultant de chaque argument est substituée à chaque occurrence (ne figurant pas entre guillemets) de l'identificateur du paramètre correspondant dans la suite de lexèmes de remplacement de la macro. A moins que le paramètre de la séquence de remplacement soit précédé de #, ou précédé ou suivi de ##, les lexèmes de l'argument sont examinés à chaque appel de la macro et développés en conséquence avant insertion.

Deux opérateurs spéciaux influent sur le processus de remplacement Premièrement, si une occurrence d'un paramètre de la séquence de lexèmes de remplacement est immédiatement précédé de #, des guillemets (") sont placés autour du paramètre correspondant et alors le # et l'identificateur du paramètre sont tous les deux remplacés par l'argument entre guillemets. Un caractère \ est inséré avant chaque caractère " ou \ qui figure autour ou à l'intérieur d'une constante de type chaîne ou de type caractère dans l'argument.

Deuxièmement, si la suite de lexèmes de la définition pour les deux types de macro contient l'opérateur If, alors juste après le remplacement des paramètres, chaque ## est supprimé en même temps que tout espacement de chaque côté de telle façon à concaténer les lexèmes adjacents et former un nouveau lexème. L'effet est indéterminé si des lexèmes incorrects sont produits ou si le résultat dépend de l'ordre du traitement des opérateurs ##. De même, on ne peut pas trouver ## au début ou à la fin d'une suite de lexèmes de remplacement.

Dans les deux sortes de macros, la suite de lexèmes de remplacement est parcourue à nouveau de façon répétitive pour des identificateurs défini s en plus. Cependant, une fois qu'un identificateur donné a été remplacé dans un développement donné, il n'est pas remplacé s'il reprend la précédente valeur pendant ce rebal aya ge ; au lieu de cela, il reste inchangé.

Même si la valeur finale d'un développement de macro commence par #, elle n'est pas considérée comme une directive du préprocesseur.

Les détails du processus de développement des macros sont décrits plus précisément dans l'édition de la norme ANSI que dans la première version. La modification la plus importante est l'apport des opérateurs # et ##, qui permettent la mise entre guillemets et la concaténaùon. Certaines nouvelles règles sont bizarres, en particulier celles qui concernent la concaténation. (voir l'exemple ci-dessous)

 

Par exemple, on peut utiliser cette fonctionnalité pour les "constantes manifestes" comme

#define TAILLETAB 100
int table[TAILLETAB];

 

La définition

#define ABSDIFFI(a,b) ((a)>(b) ? (a)-(b) : (b)-(a))

 

définit une macro qui retourne la valeur absolue de la différence de ses arguments. Contrairement à une fonction qui effectuerait la même chose, les arguments et la valeur de retour peuvent être de n'importe quel type arithmétique et même de type pointeur. De plus, les arguments qui peuvent avoir des effets de bord, sont évalués deux fois, une fois pour le test et une fois pour produire le résultat.

Etant donné la définition.

#define fichtemp(rep) #rep "/%s"

 

l'appel de la macro fichtemp (/usr/tmp) produit

"/usr/tirp" "/%s"

 

qui sera ensuite concaténée en une seule chaîne de caractères. Après la directive

#define cat(x,y) x ## y

 

l'appel cat(var,123) produira var123.

Cependant l'appel cat(cat(1,2),3)) est indéfini : la présence de ## empêche de développer les arguments de l'appel extérieur. Par conséquent, celui-ci produira la chaîne de lexèmes

cat(1,2)3

 

et )3 (la concaténation du dernier lexème du premier argument et du premier lexème du second) n'est pas un lexème valide. Si un second niveau de définition est introduit,

#define xcat(x,y) cat(x,y)

 

les choses fonctionnent sans problème ; xcat(xcat(1,2),3) produira 123 parce que le développement de xcat ne met pas en jeu l'opérateur ##.

De même, ABSDIFF(ABSDIFF(a,b),c) produira le résultat totalement développé attendu.

12.4 L'inclusion de fichier

Une ligne de contrôle de la forme

# include <nom-de-fichier>

 

provoque le remplacement de cette ligne par le contenu total du fichier nom-de-fichier. Le nom de fichier ne doit pas comporter les caractères > ou de fin de ligne et l'effet est indéfini s'il contient un des caractères ", ', \ ou /*. Le fichier indiqué est recherché dans une suite d'endroits dépendant de l'implémentation.

De même, une ligne de contrôle de la forme

# include "nom-de-fichier"

 

recherche d'abord le fichier source original associé (une expression qui dépend délibérément de l'implémentation) et, en cas d'échec, effectuera la même recherche que pour la première forme. L'effet de l'utilisation de ', \ ou /* dans le nom de fichier reste indéfini mais > est autorisé.

Finalement, une directive de la forme

# include suite-de-lexèmes

qui ne correspond pas aux formes précédentes sera interprétée en développant la suite de lexèmes comme un texte normal ; il doit résulter une des deux formes <...> ou "...", et la directive est alors traitée comme décrit précédemment.

On peut imbriquer les fichiers #include.

12.5 La compilation conditionnelle

On peut compiler de façon conditionnelle différentes parties d'un programme d'après la syntaxe schématique suivante

condition-préprocesseur :

 

ligne-condition texte parties-sinon-condition partie-sinon-opt # endif

 

ligne-condition :

# if expression-constante
# ifdef identiicateur
# ifndef identificateur

 

parties-sinon-condition :

ligne-sinon-condition texte
parties-sinon-condition-
opt

 

ligne-sinon-condition :

# elif expression-constante

 

partie-sinon :

ligne-sinon texte

 

ligne-sinon texte:

# else

 

Chacune des directives (ligne-condition, partie-sinon-condition, ligne-sinon et #endif) se trouve seule sur une ligne. Les expressions constantes des lignes #if et ensuite #elif sont évaluées dans l'ordre jusqu'à ce qu'on trouve une expression avec une valeur non nulle ; le texte qui suit une ligne avec une valeur nulle est détruit. Le texte qui suit une ligne comportant une directive réussie est traité normalement. Un "texte" fait ici référence à n'importe quel bloc contenant des lignes du préprocesseur ne faisant pas partie de la structure conditionnelle ; il peut être vide. Une fois qu'on a trouvé une ligne #if ou #elif réussie et que son texte a été traité, les lignes #elif et #else suivantes ainsi que leur texte sont supprimées. Si toutes les expressions sont nulles et qu'il y a un #else, le texte suivant le #else est traité normalement. Le texte contrôlé par des branches conditionnelles inactives est ignoré à part la vérification de l'imbrication des blocs conditionnels.

L'expression constante dans #if et #elif est soumise au remplacement de macro ordinaire, D'ailleurs, toute expression de la forme

defined identificateur

 

ou

defined ( identificateur )

 

est remplacée, avant le balayage des macros, par 1L si l'identificateur est défini dans le préprocesseur et sinon par 0L. Finalement, chaque constante entière est considérée être suffixée par 1L de telle façon que toute valeur arithmétique soit considérée long ou unsigned long.

L'expression constante résultante (§7.19) est restrictive : elle doit être entière et ne peut contenir sizeof, un "cast" ou une constante énumérée.

Les lignes de contrôle

# ifdef identificateur
# ifndef identificateur

sont respectivement équivalentes à

# if defined identificateur
# if ! defined identificateur

#elif est nouveau par rapport à la première édition bien que cette directive ait été disponible avec certains préprocesseurs. L'opérateur du préprocesseur defined est également nouveau.

 

12.6 Le contrôle des lignes

Pour aider d'autres compilateurs générant des programmes C, une ligne parmi les formes suivantes

# line constante "nom-de-fichier"
# line constante

 

permet au compilateur de savoir, dans un but de diagnostic d'erreurs, que le numéro de la ligne source suivante est donné par la constante entière décimale, et le nom du fichier courant en entrée par l'identificateur. Si le nom de fichier entre guillemets est absent, le nom précédent n'a pas changé. Les macros de la ligne sont développées avant d'être interprétées.

12.7 La génération des erreurs

Une ligne du préprocesseur de la forme

# error suite-de-lexèmes-opt

 

provoque l'écriture par le processeur d'un message de diagnostic qui contient la suite de lexèmes.

12.8 Les pragmas

Une ligne de contrôle de la forme

# pragma suite-de-lexèmes-opt

 

provoque l'exécution par le processeur d'une action dépendant de l'implémentation. Un pragma non reconnu est ignoré.

12.9 La directive nulle

Une ligne du préprocesseur de la forme

#

 

n'a aucun effet.

12.10 Les noms prédéfinis

Plusieurs identificateurs sont prédéfinis et se développent pour produire des informations spéciales. Leurs définitions, ainsi que l'opérateur d'expression du préprocesseur del ined, ne peuvent pas être supprimées ni modifiées.

_ _LINE_ _

 

Une constante décimale contenant le numéro de ligne courante du source.

_ _FILE_ _

Une constante de type chaîne contenant le nom du fichier en cours de compilation.

_ _DATE_ _

Une constante de type chaîne contenant la date de compilation sous la forme "Mmm:jj:aaaa" .

_ _TIME_ _

Une constante de type chaîne contenant l'heure de compilation sous la forme "hh:mm:ss".

_ _STDC_ _

La constante 1. Cet identificateur est supposé valoir 1 par définition uniquement dans des implémentations conformes à la norme.

#error et #pragma sont des nouveautés de la norme ANSI ; les macros prédéfinies du préprocesseur sont nouvelles, mais certaines étaient déjà disponibles dans certaines implémentations.

 

>
Sommaire
 

 
Rechercher