I - éléments de base
I-A - Structure générale d'un programme
La transformation d'un texte écrit en langage C en un programme exécutable par l'ordinateur se fait en deux étapes
: la compilation et l'édition de liens. La compilation est la traduction des fonctions écrites en C en des procédures
équivalentes écrites dans un langage dont la machine peut exécuter les instructions. Le compilateur lit toujours un
fichier, appelé fichier source, et produit un fichier, dit fichier objet.
Chaque fichier objet est incomplet, insu±sant pour être exécuté, car il contient des appels de fonctions ou des
références à des variables qui ne sont pas définies dans le même fichier. Par exemple, le premier programme que
vous écrirez contiendra déjà la fonction printf que vous n'aurez certainement pas écrite vous-même. L'édition de
liens est l'opération par laquelle plusieurs fichiers objets sont mis ensemble pour se compléter mutuelle- ment : un
fichier apporte des définitions de fonctions et de variables auxquelles un autre fichier fait référence et réciproquement.
L'éditeur de liens (ou linker ) prend en entrée plusieurs fichiers objets et bibliothèques (une variété particulière de
fichiers objets) et produit un unique fichier exécutable. L'éditeur de liens est largement indépendant du langage de
programmation utilisé pour écrire les fichiers sources, qui peuvent même avoir été écrits dans des langages différents.
Chaque fichier source entrant dans la composition d'un programme exécutable est fait d'une succession d'un nombre
quelconque d'éléments indépendants, qui sont :
· des directives pour le préprocesseur (lignes commen»cant par #),
· des constructions de types (struct, union, enum, typedef),
· des déclarations de variables et de fonctions externes,
· des définitions de variables et
· des définitions de fonctions.
Seules les expressions des deux dernières catégories font grossir le fichier objet : les définitions de fonc- tions
laissent leur traduction en langage machine, tandis que les définitions de variables se traduisent par des réservations
d'espace, éventuellement garni de valeurs initiales. Les autres directives et déclarations s'adressent au compilateur
et il n'en reste pas de trace lorsque la compilation est finie.
En C on n'a donc pas une structure syntaxique englobant tout, comme la construction « Program ... end. » du langage
Pascal ; un programme n'est qu'une collection de fonctions assortie d'un ensemble de variables globales. D'ou la
question : par ou l'exécution doit-elle commencer ? La règle généralement suivie par l'éditeur de liens est la suivante
: parmi les fonctions données il doit en exister une dont le nom est main. C'est par elle que l'exécution commencera ;
le lancement du programme équivaut à l'appel de cette fonction par le système d'exploitation. Notez bien que, à part
cela, main est une fonction comme les autres, sans aucune autre propriété spécifique ; en particulier, les variables
internes à main sont locales, tout comme celles des autres fonctions. Pour finir cette entrée en matière, voici la version
C du célèbre programme-qui-dit-bonjour, sans lequel on ne saurait commencer un cours de programmation1 :
#include <stdio.h>
int main() {
printf("Bonjourn");
return 0;
}
*
Le langage C par Henri Garreta
- 6 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
1Le programme montré ici est écrit selon des règles strictes. En fait, la plupart des compilateurs acceptent que main
soit déclarée void au lieu de int, ou que ce type ne figure pas, et que l'instruction « return 0 ; » n'apparaisse pas
explicitement.
I-B - Considérations lexicales
I-B-1 - Présentation du texte du programme
Le programmeur est maitre de la disposition du texte du programme. Des blancs, des tabulations et des sauts à la
ligne peuvent être placés à tout endroit ou cela ne coupe pas un identificateur, un nombre ou un symbole composé2.
Les commentaires commencent par /* et se terminent par */ :
/* Ce texte est un commentaire et sera donc
ignoré par le compilateur */
Les commentaires ne peuvent pas être imbriqués : écrit dans un programme, le texte « /* voici un grand
/* et un petit */ commentaire */ » est erroné, car seul « /* voici un grand /* et un petit */ »
sera
vu comme un commentaire par le compilateur.
Les langages C et C++ cohabitant dans la plupart des compilateurs actuels, ces derniers acceptent également comme
commentaire tout texte compris entre le signe // et la fin de la ligne ou ce signe apparait :
// Ceci est un commentaire à la mode C++.
Le caractère anti-slash précédant immédiatement un saut à la ligne masque ce dernier : la ligne suivante est
considérée comme devant être concaténée à la ligne courante. Cela est vrai en toute circonstance, y compris à
l'intérieur d'une chaine de caractères. Par exemple, le texte
message = "anti
constitutionnellement";
est compris comme ceci : « message = "anti constitutionnellement" ; »
*
2 Néanmoins, les directives pour le préprocesseur (cf. section 8.1) doivent comporter un # dans la première position
de la ligne. Cela ne constitue pas une exception à la règle donnée ici, car le préprocesseur n'est pas le compilateur
C et ne travaille pas sur la syntaxe du langage.
I-B-2 - Mots-clés
Les mots suivants sont réservés. Leur fonction est prévue par la syntaxe de C et ils ne peuvent pas être utilisés
dans un autre but :
Le langage C par Henri Garreta
- 7 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
auto break case char const continue default do
double else enum extern float for goto if
int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while
I-B-3 - Identificateurs
Un identificateur est une suite de lettres et chiffres contigus, dont le premier est une lettre. Lorsque seul le compilateur
est concerné, c'est-à-dire lorsqu'il s'agit d'identificateurs dont la portée est incluse dans un seul fichier (nous dirons
de tels identificateurs qu'ils sont privés) :
· en toute circonstance une lettre majuscule est tenue pour différente de la lettre minuscule correspondante ;
· dans les identificateurs, le nombre de caractères discriminants est au moins de 31.
Attention, lorsqu'il s'agit d'identificateurs externes, c'est-à-dire partagés par plusieurs
fichiers sources, il est possible que sur un système particulier l'éditeur de liens sous-jacent
soit trop rustique pour permettre le respect de ces deux prescriptions.
Le caractère _(appelé « blanc souligné ») est considéré comme une lettre ; il peut donc figurer à n'importe quelle
place dans un identificateur. Cependant, par convention un programmeur ne doit pas utiliser des identi- ficateurs qui
commencent par ce caractère. Cela assure qu'il n'y aura jamais de conflit avec les noms introduits (à travers les
fichiers « .h ») pour les besoins des bibliothèques, car ces noms commencent par un tel blanc souligné. ~
I-B-4 - Opérateurs
Symboles simples :
( ) [ ] . ! ~ < > ? :
= , + - * / % | & ^
Symboles composés :
-> ++ -- <= >= == != && || << >>
+= -= *= /= %= <<= >>= |= &= ^=
Tous ces symboles sont reconnus par le compilateur comme des opérateurs. Il est interdit d'insérer des caractères
blancs à l'intérieur d'un symbole composé. En outre, il est conseillé d'encadrer par des blancs toute utilisation
d'un opérateur. Dans certaines circonstances cette règle est plus qu'un conseil, car sa non-observance crée une
expression ambiguÄe.
I-C - Constantes littérales
I-C-1 - Nombres entiers
Les constantes littérales numériques entières ou réelles suivent les conventions habituelles, avec quelques
particularités.
Le langage C par Henri Garreta
- 8 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Les constantes littérales sont sans signe : l'expression ¡123 est comprise comme l'application de l'opérateur unaire ¡ à
la constante 123 ; mais puisque le calcul est fait pendant la compilation, cette subtilité n'a aucune conséquence pour le
programmeur. Notez aussi qu'en C original, comme il n'existe pas d'opérateur + unaire, la notation +123 est interdite.
Les constantes littérales entières peuvent aussi s'écrire en octal et en hexadécimal :
· une constante écrite en octal (base 8) commence par 0 (zéro) ;
· une constante écrite en hexadécimal (base 16) commence par 0x ou 0X.
Voici par exemple trois manières d'écrire le même nombre :
27 033 0x1B
Détail à retenir : on ne doit pas écrire de zéro non significatif à gauche d'un nombre : 0123 ne représente pas la
même valeur que 123.
Le type d'une constante entière est le plus petit type dans lequel sa valeur peut être représentée. Ou, plus exactement
:
· si elle est décimale : si possible int, sinon long, sinon unsigned long ;
· si elle est octale ou hexadécimale : si possible int, sinon unsigned int, sinon unsigned long.
Certains su±xes permettent de changer cette classification :
· U, u : indique que la constante est d'un type unsigned ;
· L, l : indique que la constante est d'un type long.
Exemples : 1L, 0x7FFFU. On peut combiner ces deux su±xes : 16UL.
I-C-2 - Nombres flottants
Une constante littérale est l'expression d'un nombre flottant si elle présente, dans l'ordre
:
· une suite de chiffres décimaux (la partie entière),
· un point, qui joue le role de virgule décimale,
· une suite de chiffres décimaux (la partie fractionnaire),
· une des deux lettres E ou e,
· éventuellement un signe + ou -,
· une suite de chiffres décimaux.
Les trois derniers éléments forment l'exposant. Exemple : 123.456E-78.
On peut omettre :
· la partie entière ou la partie fractionnaire, mais pas les deux,
· le point ou l'exposant, mais pas les deux.
Exemples : .5e7, 5.e6, 5000000., 5e6
Le langage C par Henri Garreta
- 9 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Une constante flottante est supposée de type double, à moins de comporter un su±xe
explicite :
· les su±xes F ou f indiquent qu'elle est du type float ;
· les su±xes L ou l indiquent qu'elle est du type long double.
Exemples : 1.0L, 5.0e4f
I-C-3 - Caractères et chaines de caractères
Une constante de type caractère se note en écrivant le caractère entre apostrophes. Une constante de type chaine
de caractères se note en écrivant ses caractères entre guillemets. Exemples, trois caractères :
'A' '2' '"'
Quatre chaines de caractères :
"A" "Bonjour à tous !" "" "'"
On peut faire figurer n'importe quel caractère, même non imprimable, dans une constante caractère ou chaine de
caractères en utilisant les combinaisons suivantes, appelées séquences d'échappement :
n nouvelle ligne (LF)
t tabulation (HT)
b espace-arrière (BS)
r retour-chariot (CR)
f saut de page (FF)
a signal sonore (BELL)
' '
" "
d3d2d1 le caractère qui a pour code le nombre octal d3d2d1. S'il commence par un ou deux zéros et si cela ne crée
pas une ambiguijté, on peut aussi le noter d2d1 ou d1
Par exemple, la chaine suivante définit la suite des 9 caractères 3 A, escape (de code ASCII 27), B, ", C, saut de
page, D, et E :
"A�33B"CfDE"
Une constante de type caractère appartient au type char, c'est-à-dire entier représenté sur un octet. La valeur d'une
constante caractère est le nombre qui représente le caractère de manière interne ; de nos jours il s'agit presque
toujours du code ASCII 4.
Une constante de type chaine de caractères représente une suite finie de caractères, de longueur quelconque. Le
codage interne d'une chaine de caractères est le suivant (voyez la figure 1) :
· les caractères constituant la chaine sont rangés en mémoire, de manière contiguÄe, dans l'ordre ou ils
figurent dans la chaine ;
Le langage C par Henri Garreta
- 10 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· un caractère nul est ajouté immédiatement après le dernier caractère de la chaine, pour en indiquer la fin ;
· la constante chaine représente alors, à l'endroit ou elle est écrite, l'adresse de la cellule ou a été rangé le
premier caractère de la chaine
Fig. 1 - Représentation de la chaine 'Bonjour'
Par conséquent, une constante chaine de caractères a pour type celui d'un tableau de caractères (c'est-à-dire « char[]
») et pour valeur l'adresse d'une cellule de la mémoire. Par caractère nul on entend le caractère dont le code interne
est 0 ; on peut le noter indifféremment 0, '�00' ou '�' (mais certainement pas '0') ; il est utilisé très fréquemment en
C. Notez que, dans une expression, '�' est toujours interchangeable avec 0.
*
3Nous verrons qu'en fait cette chaine comporte un caractère de plus qui en marque la fin.
*
4En standard le langage C ne prévoit pas le codage Unicode des caractères.
I-C-4 - Expressions constantes
Une expression constante est une expression de l'un des types suivants :
· toute constante littérale ; exemples : 1, 'A', "HELLO", 1.5e-2 ;
· une expression correcte formée par l'application d'un opérateur courant (arithmétique, logique, etc.) à une ou
deux expressions constantes ; exemples : -1, 'A' - 'a', 2 * 3.14159265, "HELLO" + 6 ;
· l'expression constituée par l'application de l'opérateur & (opérateur de calcul de l'adresse, voyez la section
2.2.10) à une variable statique, à un champ d'une variable statique de type structure ou à un élément d'un
tableau statique dont le rang est donné par une expression constante ; exemples : &x, &fiche.nom, &table[50]
;
· l'expression constituée par l'application de l'opérateur sizeof à un descripteur de type. Exemples : sizeof(int),
sizeof(char *) ;
· l'expression constituée par l'application de l'opérateur sizeof à une expression quelconque, qui ne sera pas
évaluée ; exemples : sizeof x, sizeof(2 * x + 3).
Les expressions constantes peuvent être évaluées pendant la compilation. Cela est fait à titre facultatif par les
compilateurs de certains langages. En C ce n'est pas facultatif : il est garanti que toute expression constante (et donc
toute sous-expression constante d'une expression quelconque) sera effectivement évaluée avant que l'exécution
ne commence. En termes de temps d'exécution, l'évaluation des expressions constantes est donc entièrement «
gratuite ».
I-D - Types fondamentaux
Le langage C par Henri Garreta
- 11 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.


I-D-1 - Nombres entiers et caractères
La classification des types numériques obéit à deux critères :
· Si on cherche à représenter un ensemble de nombres tous positifs on pourra adopter un type non signé ;
au contraire si on doit représenter un ensemble contenant des nombres positifs et des nombres négatifs on
devra utiliser un type signé 5.
· Le deuxième critère de classification des données numériques est la taille requise par leur représentation.
Comme précédemment, c'est un attribut d'un ensemble, et donc d'une variable devant représenter tout élément de
l'ensemble, non d'une valeur particulière. Par exemple, le nombre 123 considéré comme un élément de l'ensemble
f0 ::: 65535g est plus encombrant que le même nombre 123 quand il est considéré comme un élément de l'ensemble
f0 ::: 255g.
Avec N chiffres binaires (ou bits) on peut représenter :
· soit les 2N nombres positifs 0; 1; ::: 2N ¡ 1 (cas non signé) ;
· soit les 2N nombres positifs et négatifs ¡2N¡1; ::: 2N¡1 ¡ 1 (cas signé).
De plus, la représentation signée et la représentation non signée des éléments communs aux deux domaines (les
nombres 0; 1; ::: 2N-1 - 1) coijncident.
Le type caractère. Un objet de type char peut être défini, au choix, comme
· un nombre entier pouvant représenter n'importe quel caractère du jeu de caractères de la machine utilisée ;
· un nombre entier occupant la plus petite cellule de mémoire adressable séparément 6. Sur les machines
actuelles les plus répandues cela signifie généralement un octet (8 bits).
Le plus souvent, un char est un entier signé ; un unsigned char est alors un entier non signé. Lorsque les char sont
par défaut non signés, la norme ANSI prévoit la possibilité de déclarer des signed char. On notera que la signification
d'un char en C, un entier petit, est très différente de celle d'un char en Pascal (dans ce langage, l'ensemble des
caractères et celui des nombres sont disjoints). En C, ch étant une variable de type char, rien ne s'oppose à l'écriture
de l'expression ch - 'A' + 32 qui est tout à fait homogène, puisque entièrement faite de nombres.
Le caractère « impossible ». Toutes les valeurs qu'il est possible de ranger dans une variable de type char sont en
principe des caractères légaux. Or la plupart des programmes qui lisent des caractères doivent être capables de
manipuler une valeur supplémentaire, distincte de tous les « vrais » caractères, signifiant « la fin des données ».
Pour cette raison, les variables et fonctions qui représentent ou renvoient des caractères sont souvent déclarées
int, non char : n'importe quelle valeur appartenant au type int mais n'appartenant pas au type char peut alors servir
d'indicateur de fin de données. Par exemple, une telle valeur est définie dans le fichier stdio.h, c'est la constante
symbolique EOF.
Les entiers courts et longs. Il est garanti que toute donnée représentable dans le type short est représentable aussi
dans le type long 7 (en bref : un long n'est pas plus court qu'un short !), mais la taille exacte des données de ces
types n'est pas fixée par la norme du langage. De nos jours on trouve souvent :
· unsigned short : 16 bits pour représenter un nombre entier compris entre 0 et 65.535
· short : 16 bits pour représenter un nombre entier compris entre -32.768 et 32.767
· unsigned long : 32 bits pour représenter un nombre entier entre 0 et 4.294.967.296
· long : 32 bits pour représenter un entier entre -2.147.483.648 et 2.147.483.647
Le langage C par Henri Garreta
- 13 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Le type int. En principe, le type int correspond à la taille d'entier la plus e±cace, c'est-à-dire la plus adaptée à la
machine utilisée. Sur certains systèmes et compilateurs int est synonyme de short, sur d'autres il est synonyme de
long.
Le type int peut donc poser un problème de portabilité 8 : le même programme, compilé sur deux machines distinctes,
peut avoir des comportements différents. D'ou un conseil important : n'utilisez le type int que pour des variables
locales destinées à contenir des valeurs raisonnablement petites (inférieures en valeur absolue à 32767) . Dans les
autres cas il vaut mieux expliciter char, short ou long selon le besoin.
A propos des booléens. En C il n'existe donc pas de type booléen spécifique. Il faut savoir qu'à tout endroit ou
une expression booléenne est requise (typiquement, dans des instructions comme if ou while) on peut faire figurer
n'importe quelle expression ; elle sera tenue pour vraie si elle est non nulle, elle sera considérée fausse sinon. Ainsi,
dans un contexte conditionnel,
expr
(c'est-à-dire expr « vraie ») équivaut à
expr != 0
(expr différente de 0). Inversement, lorsqu'un opérateur (égalité, comparaison, etc.) produit une valeur booléenne,
il rend 0 pour faux et 1 pour vrai.
Signalons aux esthètes que le fichier <types.h> comporte les déclarations :
enum { false, true };
typedef unsigned char Boolean;
qui introduisent la constante false valant 0, la constante true valant 1 et le type Boolean comme le type le moins
encombrant dans lequel on peut représenter ces deux valeurs.
*
5 On dit parfois qu'une donnée « est un entier signé » ou « est un entier non signé ». C'est un abus de langage
: le caractère signé ou non signé n'est pas un attribut d'un nombre (un nombre donné est positif ou négatif, c'est
tout) mais de l'ensemble de nombres qu'on a choisi de considérer et, par extension, de toute variable censée pouvoir
représenter n'importe quelle valeur de cet ensemble.
*
6A retenir : un objet de type char est « unitaire » aussi bien du point de vue des tailles que de celui des adresses.
Quelle que soit la machine utilisée, le compilateur C fera en sorte que le programmeur voie ces objets de la manière
suivante : si t est un tableau de char, la taille (au sens de l'opérateur sizeof, cf. section 2.2.11) de t[0] vaut une
unité de taille, et l'écart entre les adresses de t[1] et t[0] vaut une unité d'adressage. On peut dire que ces propriétés
définissent le type char (ou, si vous préférez, les unités de taille et d'adressage).
*
7Si on considère un type comme l'ensemble de ses valeurs, on a donc les inclusions larges char μ short μ long (et
aussi float μ double μ long double).
*
8Un programme écrit pour une machine ou un système A est dit portable s'il su±t de le recompiler pour qu'il tourne
correctement sur une machine différente B. Par exemple, « putchar('A') ; » est une manière portable d'obtenir
Le langage C par Henri Garreta
- 14 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
l'a±chage du caractère A, tandis que « putchar(65) ; » est (sur un système utilisant le code ASCII) une manière
non portable d'obtenir le même a±chage. Etre portable est un critère de qualité et de fiabilité important. On invoque
l'e±cacité pour justifier l'écriture de programmes non portables ; l'expérience prouve que, lorsque son écriture est
possible, un programme portable est toujours meilleur qu'un programme non portable prétendu équivalent.
I-D-2 - Types énumérés
Un type énuméré, ou énumération, est constitué par une famille finie de nombres entiers, chacun associé à un
identificateur qui en est le nom. Mis à part ce qui touche à la syntaxe de leur déclaration, il n'y a pas grand-chose à
dire à leur sujet. La syntaxe de la déclaration des énumérations est expliquée à la section 5.3. Par exemple, l'énoncé :
enum jour_semaine - lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche }; introduit un type énuméré, appelé
enum jour semaine, constitué par les constantes lundi valant 0, mardi valant 1, mercredi valant 2, etc. Ainsi, les
expressions mardi + 2 et jeudi représentent la même valeur. Les valeurs d'un type énuméré se comportent comme
des constantes entières ; elles font donc double emploi avec celles qu'on définit à l'aide de #define (cf. section 8.1.2).
Leur unique avantage réside dans le fait que certains compilateurs détectent parfois, mais ce n'est pas exigé par la
norme, les mélanges entre objets de types énumérés distincts ; ces types sont alors le moyen d'augmenter la sécurité
des programmes. A propos des types énumérés voyez aussi la section 5.3
I-D-3 - Nombres flottants
La norme ANSI prévoit trois types de nombres flottants : float (simple précision), double (double précision) et long
double (précision étendue). La norme ne spécifie pas les caractéristiques de tous ces types. Il est garanti que toute
valeur représentable dans le type float est représentable sans perte d'information dans le type double, et toute valeur
représentable dans le type double l'est dans le type long double.
Typiquement, sur des systèmes de taille moyenne, un float occupe 32 bits et un double 64, ce qui donne par exemple
des float allant de -1.70E38 à -0.29E-38 et de 0.29E-38 à 1.70E38 avec 7 chiffres décimaux significatifs, et des double
allant de -0.90E308 à -0.56E-308 et de 0.56E-308 à 0.90E308 avec 15 chiffres décimaux significatifs.
Les long double correspondent généralement aux flottants de grande précision manipulés par certains coprocesseurs
arithmétiques ou les bibliothèques de sous-programmes qui les simulent. Mais il n'est pas exclu que sur un système
particulier un long double soit la même chose qu'un double.
I-E - Variables
I-E-1 - Syntaxe des déclarations
La forme complète de la déclaration d'une variable sera expliquée à la section 5.4. Dans le cas le plus simple on trouve
spécification var-init , var-init , ... var-init ;
ou spécification est de la forme :
et chaque var-init est de la forme :
Exemples :
Le langage C par Henri Garreta
- 15 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
int x, y = 0, z;
extern float a, b;
static unsigned short cpt = 1000;
Les déclarations de variables peuvent se trouver :
· en dehors de toute fonction, il s'agit alors de variables globales ;
· à l'intérieur d'un bloc, il s'agit alors de variables locales ;
· dans l'en-tête d'une fonction, il s'agit alors d'arguments formels, placés
· soit dans les parenthèses de l'en-tête (fonction définie en syntaxe ANSI avec un prototype),
· soit entre le nom de la fonction et le f initial (fonction définie en syntaxe originale ou sans prototype).
Exemple. Avec prototype :
long i = 1;
int une_fonction(int j) {
short k;
...
}
Sans prototype :
long i = 1;
int une_fonction(j)
int j;
{
short k;
...
}
Ci-dessus, i est une variable globale, k une variable locale et j un argument formel de une fonction
I-E-2 - Visibilité des variables
La question de la visibilité des identificateurs (c'est-à-dire « quels sont les identificateurs auxquels on peut faire
référence en un point d'un programme ? ») est réglée en C comme dans la plupart des langages comportant la
structure de bloc, avec une simplification : les fonctions ne peuvent pas être imbriquées les unes dans les autres, et
une complication : tout bloc peut comporter ses propres définitions de variables locales.
Un bloc est une suite de déclarations et d'instructions encadrée par une accolade ouvrante f" et l'accolade fermante
g" correspondante. Le corps d'une fonction est lui-même un bloc, mais d'autres blocs peuvent être imbriqués dans
celui-là.
VARIABLES LOCALES. Tout bloc peut comporter un ensemble de déclarations de variables, qui sont alors dites
locales au bloc en question. Une variable locale ne peut être référencée que depuis l'intérieur du bloc ou elle est
définie ; en aucun cas on ne peut y faire référence depuis un point extérieur à ce bloc. Dans le bloc ou il est déclaré,
le nom d'une variable locale masque toute variable de même nom définie dans un bloc englobant le bloc en question.
Toutes les déclarations de variables locales à un bloc doivent être écrites au début du bloc, avant la première
instruction.
ARGUMENTS FORMEL. Pour ce qui concerne leur visibilité, les arguments formels des fonctions sont considérés
comme des variables locales du niveau le plus haut, c'est-à-dire des variables déclarées au début du bloc le plus
extérieur 9. Un argument formel est accessible de l'intérieur de la fonction, partout ou une variable locale plus profonde
ne le masque pas. En aucun cas on ne peut y faire référence depuis l'extérieur de la fonction.
Le langage C par Henri Garreta
- 16 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
VARIABLES GLOBALES. Le nom d'une variable globale ou d'une fonction peut être utilisé depuis n'importe quel
point compris entre sa déclaration (pour une fonction : la fin de la déclaration de son en-tête) et la fin du fichier ou la
déclaration figure, sous réserve de ne pas être masquée par une variable locale ou un argument formel de même nom.
La question de la visibilité inter-fichiers est examinée à la section 1.6. On peut noter d'ores et déjà qu'elle ne se
pose que pour les variables globales et les fonctions, et qu'elle concerne l'édition de liens, non la compilation, car le
compilateur ne traduit qu'un fichier source à la fois et, pendant la traduction d'un fichier, il ne « voit » pas les autres.
*
9Par conséquent, on ne doit pas déclarer un argument formel et une variable locale du niveau le plus haut avec
le même nom.
I-E-3 - Allocation et durée de vie des variables
Les variables globales sont toujours statiques, c'est-à-dire permanentes : elles existent pendant toute la durée de
l'exécution. Le système d'exploitation se charge, immédiatement avant l'activation du programme, de les allouer dans
un espace mémoire de taille adéquate, éventuellement garni de valeurs initiales.
A l'opposé, les variables locales et les arguments formels des fonctions sont automatiques : l'espace corres- pondant
est alloué lors de l'activation de la fonction ou du bloc en question et il est rendu au système lorsque le controle quitte
cette fonction ou ce bloc. Certains qualifieurs (static, register, voir les sections 1.5.5 et 1.5.6) permettent de modifier
l'allocation et la durée de vie des variables locales. Remarque. On note une grande similitude entre les variables
locales et les arguments formels des fonctions : ils ont la même visibilité et la même durée de vie. En réalité c'est
presque la même chose : les arguments formels sont de vraies variables locales avec l'unique particularité d'être
automatiquement initialisés (par les valeurs des arguments effectifs) lors de l'activation de la fonction.
I-E-4 - Initialisation des variables
Variables statiques. En toute circonstance la déclaration d'une variable statique peut indiquer une valeur initiale à
ranger dans la variable. Cela est vrai y compris pour des variables de types complexes (tableaux ou structures).
Exemple :
double x = 0.5e3;
int t[5] = { 11, 22, 33, 44, 55 };
Bien que la syntaxe soit analogue, une telle initialisation n'a rien en commun avec une affectation comme celles qui
sont faites durant l'exécution du programme. Il s'agit ici uniquement de préciser la valeur qui doit être déposée dans
l'espace alloué à la variable, avant que l'exécution ne commence.
Par conséquent :
· la valeur initiale doit être définie par une expression constante (calculable durant la compilation) ;
· une telle initialisation est entièrement gratuite, elle n'a aucune incidence ni sur la taille ni sur la durée du
programme exécutable produit. Les variables statiques pour lesquelles aucune valeur initiale n'est indiquée
sont remplies de zéros. L'in- terprétation de ces zéros dépend du type de la variable.
Variables automatiques. Les arguments formels des fonctions sont automatiquement initialisés lors de leur création
(au moment de l'appel de la fonction) par les valeurs des arguments effectifs. Cela est la définition même des
arguments des fonctions.
Le langage C par Henri Garreta
- 17 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
La déclaration d'une variable locale peut elle aussi comporter une initialisation. Mais il ne s'agit pas de la même sorte
d'initialisation que pour les variables statiques : l'initialisation représente ici une affectation tout à fait ordinaire. Ainsi,
placée à l'intérieur d'un bloc, la construction
int i = exp; /* déclaration + initialisation */
équivaut au couple
int i; /* déclaration */
...
i = exp ; /* affectation */
Par conséquent :
· l'expression qui donne la valeur initiale n'a pas à être constante, puisqu'elle est évaluée à l'exécution, chaque
fois que la fonction ou le bloc est activé ;
· une telle initialisation « co^ute » le même prix que l'affectation correspondante, c'est-à-dire le temps
d'évaluation de l'expression qui définit la valeur initiale.
Les variables automatiques pour lesquelles aucune valeur initiale n'est indiquée sont allouées avec une valeur
imprévisible.
Remarque. Dans le C original, une variable automatique ne peut être initialisée que si elle
est simple (c'est-à-dire autre que tableau ou structure). Cette limitation ne fait pas partie
du C ANSI.
I-E-5 - Variables locales statiques
Le qualifieur static, placé devant la déclaration d'une variable locale, produit une variable qui est
· pour sa visibilité, locale ;
· pour sa durée de vie, statique (c'est-à-dire permanente).
Elle n'est accessible que depuis l'intérieur du bloc ou elle est déclarée, mais elle est créée au début de l'activation
du programme et elle existe aussi longtemps que dure l'exécution de celui-ci. Exemple :
void bizarre1(void) {
static int cpt = 1000;
printf("%d ", cpt);
cpt++;
}
Lorsque la déclaration d'une telle variable comporte une initialisation, il s'agit de l'initialisation d'une va- riable statique
: elle est effectuée une seule fois avant l'activation du programme. D'autre part, une variable locale statique conserve
sa valeur entre deux activations consécutives de la fonction. Ainsi, des appels successifs de la fonction ci-dessus
produisent l'a±chage des valeurs 1000, 1001, 1002, etc. On aurait pu obtenir un effet analogue avec le programme
int cpt = 1000;
void bizarre2(void) {
printf("%d ", cpt);
cpt++;
}
Le langage C par Henri Garreta
- 18 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
mais ici la variable cpt est globale et peut donc être modifiée inconsidérément par une autre fonction, ou entrer en
conflit avec un autre objet de même nom, tandis que dans la première version elle n'est visible que depuis l'intérieur
de la fonction et donc à l'abri des manipulations maladroites et des collisions de noms. On notera pour finir que la
version suivante est erronée :
void bizarre3(void) {
int cpt = 1000;
printf("%d ", cpt);
cpt++;
}
En effet, tous les appels de bizarre3 a±cheront la même valeur 1000.
Attention. Malgré tout le bien qu'on vient d'en dire, les variables locales statiques ont une
particularité potentiellement fort dangereuse : il en existe une seule instance pour toutes
les activations de la fonction dans laquelle elles sont déclarées. Ainsi, dans l'exemple
suivant :
void fonction_suspecte(void) {
static int i;
...
ff fonction_suspecte(); fi
...
}
la valeur de la variable i avant et après l'appel de fonction suspecte (c'est-à-dire aux points ff et fi) peut ne pas être
la même, car la deuxième activation de fonction suspecte accède aussi à i. Cela est tout à fait inhabituel pour une
variable locale. Conséquence à retenir : les variables locales statiques se marient mal avec la récursivité.
I-E-6 - Variables critiques
Le qualifieur register précédant une déclaration de variable informe le compilateur que la variable en question est
très fréquemment accédée pendant l'exécution du programme et qu'il y a donc lieu de prendre toutes les dispositions
utiles pour en accélérer l'accès. Par exemple, dans certains calculateurs de telles variables sont logées dans un
registre de l'unité centrale de traitement (CPU) plutot que dans la mémoire centrale ; de cette manière l'accès à leur
valeur ne met pas en ¾uvre le bus de la machine.
Les variables ainsi déclarées doivent être locales et d'un type simple (nombre, pointeur). Elles sont automa- tiquement
initialisées à zéro chaque fois qu'elles sont créées. Le compilateur accorde ce traitement spécial aux variables
dans l'ordre ou elles figurent dans les déclarations. Lorsque cela n'est plus possible (par exemple, parce que tous
les registres de la CPU sont pris) les déclarations register restantes sont ignorées. Il convient donc d'appliquer ce
qualifieur aux variables les plus critiques d'abord. Exemple :
char *strcpy(char *dest, char *srce) {
register char *d = dest, *s = srce;
while ((*d++ = *s++) != 0)
;
return dest;
}
Attention. L'utilisation du qualifieur register est intéressante lorsque l'on doit utiliser un
compilateur rustique, peu « optimisateur ». Or de nos jours les compilateurs de C ont fini
Le langage C par Henri Garreta
- 19 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
par devenir très perfectionnés et intègrent des algorithmes d'optimisation, parmi lesquels
la détermination des variables critiques et leur allocation dans les registres de la CPU.
Il s'avère alors que le programmeur, en appliquant le qualifieur register à ses variables
préférées (qu'il croit critiques alors qu'elles ne le sont pas réellement), gène le travail
du compilateur et obtient un programme moins e±cace que s'il n'avait jamais utilisé ce
qualifieur.
I-E-7 - Variables constantes et volatiles
Le qualifieur const placé devant une variable ou un argument formel informe le compilateur que la variable ou
l'argument en question ne changera pas de valeur tout au long de l'exécution du programme ou de l'activation de la
fonction. Ce renseignement permet au compilateur d'optimiser la gestion de la variable, la nature exacte d'une telle
optimisation n'étant pas spécifiée. Par exemple un compilateur peut juger utile de ne pas allouer du tout une variable
qualifiée const et de remplacer ses occurrences par la valeur initiale 10 indiquée lors de la déclaration. Il est conseillé
de toujours déclarer const les variables et les arguments formels qui peuvent l'être.
Note. C'est regrettable mais, pour la plupart des compilateurs, une variable qualifiée const
n'est pas tout à fait une expression constante au sens de la section 1.3.4. En particulier,
pour ces compilateurs une variable, même qualifiée const, ne peut pas être utilisée pour
indiquer le nombre d'éléments dans une déclaration de tableau.
Le C ANSI introduit aussi les notions de pointeur constant et de pointeur sur constante, expliquées à la section 5.4.2.
Le sens du qualifieur volatile dépend lui aussi de l'implémentation. Il diminue le nombre d'hypothèses, et donc
d'optimisations, que le compilateur peut faire sur une variable ainsi qualifiée. Par exemple toute variable dont la
valeur peut être modifiée de manière asynchrone (dans une fonction de détection d'interruption, ou par un canal
d'entrée-sortie, etc.) doit être qualifiée volatile, sur les systèmes ou cela a un sens. Cela prévient le compilateur
que sa valeur peut changer mystérieusement, y compris dans une section du programme qui ne comporte aucune
référence à cette variable.
Les compilateurs sont tenus de signaler toute tentative décelable de modification d'une variable const. Mis à part cela,
sur un système particulier ces deux qualifieurs peuvent n'avoir aucun autre effet. Ils n'appartiennent pas au C original.
*
10La déclaration d'une variable const doit nécessairement comporter une initialisation car sinon, une telle variable
ne pouvant pas être affectée par la suite, elle n'aurait jamais de valeur définie.
I-F - Variables, fonctions et compilation séparée
I-F-1 - Identificateurs publics et privés
Examinons maintenant les règles qui régissent la visibilité inter-fichiers des identificateurs. La question ne concerne
que les noms de variables et de fonctions, car les autres identificateurs (noms de structures, de types, etc.) n'existent
que pendant la compilation et ne peuvent pas être partagés par deux fichiers. Il n'y a pas de problème pour les
variables locales, dont la visibilité se réduit à l'étendue de la fonction ou du bloc contenant leur définition. Il ne sera
donc question que des noms des variables globales et des noms des fonctions.
Jargon. Identificateurs publics et privés. Un nom de variable ou de fonction défini dans un fichier source et pouvant
être utilisé dans d'autres fichiers sources est dit public. Un identificateur qui n'est pas public est appelé privé.
Le langage C par Henri Garreta
- 20 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Regle 1.
· Sauf indication contraire, tout identificateur global est public ;
· le qualifieur static, précédant la déclaration d'un identificateur global, rend celui-ci privé.
On prendra garde au fait que le qualifieur static n'a pas le même effet quand il s'applique à un identificateur local
(static change la durée de vie, d'automatique en statique, sans toucher à la visibilité) et quand il s'applique à un
identificateur global (static change la visibilité, de publique en privée, sans modifier la durée de vie).
Lorsqu'un programme est décomposé en plusieurs fichiers sources il est fortement conseillé, pour ne pas dire
obligatoire, d'utiliser le qualifieur static pour rendre privés tous les identificateurs qui peuvent l'être. Si on ne suit pas
cette recommandation on verra des fichiers qui étaient corrects séparément devenir erronés lorsqu'ils sont reliés,
uniquement parce qu'ils partagent à tort des identificateurs publics.
I-F-2 - Déclaration d'objets externes
Nous ne considérons donc désormais que les noms publics. Un identificateur référencé dans un fichier alors qu'il est
défini dans un autre fichier est appelé externe. En général, les noms externes doivent faire l'objet d'une déclaration
: le compilateur ne traitant qu'un fichier à la fois, les propriétés de l'objet externe doivent être indiquées pour que la
compilation puisse avoir lieu correctement.
Jargon. Définition et déclaration d'une variable ou d'une fonction. Aussi bien une déclaration qu'une définition d'un
nom de variable ou de fonction est une formule qui spécifie la classe syntaxique (variable ou fonction) et les attributs
(type, valeur initiale, etc.) de l'identificateur en question. En plus de cela :
· une définition produit la création de l'objet dont l'identificateur est le nom ;
· une déclaration se limite à indiquer que l'objet en question a d^u être créé dans un autre fichier qui sera fourni
lors de l'édition de liens. (« Créer » une variable ou une fonction c'est réserver l'espace correspondant, rempli
par l'éventuelle valeur initiale de la variable ou par le code de la fonction).
Regle 2.
· Toute variable doit avoir été définie (c'est-à-dire déclarée normalement) ou déclarée externe avant son
utilisation ;
· une fonction peut être référencée alors qu'elle n'a encore fait l'objet d'aucune définition ni déclaration externe
; elle est alors supposée être
· externe,
· à résultat entier (int),
· sans prototype (cf. section 4.2) ;
· par conséquent, si une fonction n'est pas à résultat entier alors elle doit être soit définie soit déclarée externe
avant son appel, même si elle est ultérieurement définie dans le fichier ou figure l'appel.
La déclaration externe d'une variable s'obtient en faisant précéder une déclaration ordinaire du mot-clé extern.
Exemple :
extern unsigned long n;
Dans un autre fichier cette variable aura été définie :
unsigned long n;
Le langage C par Henri Garreta
- 21 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
La déclaration externe d'une variable doit être identique, au mot extern près, à sa définition. Sauf pour les deux
points suivants :
· une déclaration externe ne doit pas comporter d'initialisateur (puisque la déclaration externe n'alloue pas la
variable),
· dans une déclaration externe de tableau, il est inutile d'indiquer la taille de celui-ci (puisque la déclaration
externe n'alloue pas le tableau).
Exemple. Dans le fichier ou sont définies les variables n et table, on écrira :
unsigned long n = 1000;
int table[100];
Dans un autre fichier, ou ces variables sont uniquement référencées, on écrira :
extern unsigned long n;
extern int table[];
La déclaration externe d'une fonction s'obtient en écrivant l'en-tête de la fonction, précédé du mot extern et suivi d'un
point-virgule ; le mot extern est facultatif. Exemple : définition de la fonction
double carre(double x) {
return x * x;
}
Déclaration externe dans un autre fichier :
double carre(double x);
ou
double carre(double);
ou l'un ou l'autre de ces énoncés, précédé du mot extern.
En syntaxe originale (c'est-à-dire « sans prototype ») il faut en outre ne pas écrire les arguments formels.
Définition :
double carre(x)
double x;
{
return x * x;
}
Déclaration externe dans un autre fichier :
Le langage C par Henri Garreta
- 22 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
double carre();
Regle 3. Dans l'ensemble des fichiers qui constituent un programme, chaque nom public
:
· doit faire l'objet d'une et une seule définition ;
· peut être déclaré externe (y compris dans le fichier ou il est défini) un nombre quelconque de fois.
Cette règle volontariste est simple et elle exprime la meilleure fa»con de programmer. Il faut savoir cependant que
chaque système tolère des écarts, qui révèlent surtout la rusticité de l'éditeur de liens sous-jacent. La clarté des
concepts et la fiabilité des programmes y perdent beaucoup.
Un comportement fréquent est le suivant : appelons momentanément « déclaration-définition » une expression
générale de la forme
Nous pouvons donner la règle rel^achée :
Regle 3'. Dans l'ensemble des fichiers qui constituent un programme, chaque nom public
peut faire l'objet d'un nombre quelconque de déclarations-définitions, mais :
· il doit y avoir au moins une déclaration-définition sans le mot-clé extern ;
· il peut y avoir au plus une déclaration-définition comportant un initialisateur.
Des techniques et conseils pour écrire des programmes modulaires en C sont exposés à la section 8.2.
Le langage C par Henri Garreta
- 23 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
II - Opérateurs et expressions
II-A - Généralités
Dans cette section nous étudions les opérateurs et les expressions du langage C. Les expressions simples sont les
constantes littérales (0, 'A', 0.31416e1, etc.), les constantes symboliques (lundi, false, etc.) et les noms de variables
(x, nombre, etc.). Les opérateurs servent à construire des expressions complexes, comme 2 * x + 3 ou sin(0.31416e1).
Les propriétés d'une expression complexe découlent essentiellement de la nature de l'opérateur qui chapeaute
l'expression.
On peut distinguer les expressions pures des expressions avec effet de bord 11. Dans tous les cas une expression
représente une valeur. Une expression pure ne fait que cela : l'état du système est le même avant et après son
évaluation. Au contraire, une expression à effet de bord modifie le contenu d'une ou plusieurs variables. Par exemple,
l'expression y + 1 est pure, tandis que l'affectation x = y + 1 (qui en C est une expression) est à effet de bord, car elle
modifie la valeur de la variable x. Comme nous le verrons, en C un grand nombre d'expressions ont de tels effets.
Remarque 1. Les opérateurs dont il sera question ici peuvent aussi apparaitre dans les
déclarations, pour la construction des types dérivés (tableaux, pointeurs et fonctions)
comme dans la déclaration complexe :
char (*t[20])();
La signification des expressions ainsi écrites est alors très différente de celle des expressions figurant dans la partie
exécutable des programmes, mais on peut signaler d'ores et déjà que toutes ces constructions obéissent aux mêmes
règles de syntaxe, notamment pour ce qui concerne la priorité des opérateurs et l'usage des parenthèses. La question
des déclarations complexes sera vue à la section 5.4.
Remarque 2. C'est une originalité de C que de considérer les « désignateurs » complexes
(les objets pointés, les éléments des tableaux, les champs des structures, etc.) comme des
expressions construites avec des opérateurs et obéissant à la loi commune, notamment
pour ce qui est des priorités. On se souvient qu'en Pascal les signes qui permettent d'écrire
de tels désignateurs (c'est-à-dire les sélecteurs [], ^ et .) n'ont pas statut d'opérateur. Il
ne viendrait pas à l'idée d'un programmeur Pascal d'écrire 2 + (t[i]) afin de lever une
quelconque ambiguijté sur la priorité de + par rapport à celle de [], alors qu'en C de telles
expressions sont habituelles. Bien s^ur, dans 2 + (t[i]) les parenthèses sont superflues,
car la priorité de l'opérateur [] est supérieure à celle de +, mais ce n'est pas le cas dans
(2 + t)[i], qui est une expression également légitime.
*
11Effet de bord est un barbarisme ayant pour origine l'expression anglaise side effect qu'on peut traduire par effet
secondaire souvent un peu caché, parfois dangereux.
II-A-1 - Lvalue et rvalue
Toute expression possède au moins deux attributs : un type et une valeur. Par exemple, si i est une variable entière
valant 10, l'expression 2 * i + 3 possède le type entier et la valeur 23. Dans la partie exécutable des programmes
on trouve deux sortes d'expressions :
Le langage C par Henri Garreta
- 24 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· Lvalue (expressions signifiant « le contenu de... »). Certaines expressions sont représentées par une formule
qui détermine un emplacement dans la mémoire ; la valeur de l'expression est alors définie comme le
contenu de cet emplacement. C'est le cas des noms des variables, des composantes des enregistrements et
des tableaux, etc. Une lvalue possède trois attributs : une adresse, un type et une valeur. Exemples : x,
table[i], fiche.numero.
· Rvalue (expressions signifiant « la valeur de... »). D'autres expressions ne sont pas associées à un emplacement
de la mémoire : elles ont un type et une valeur, mais pas d'adresse. C'est le cas des constantes et
des expressions définies comme le résultat d'une opération arithmétique. Une rvalue ne possède que deux
attributs : type, valeur. Exemples : 12, 2 * i + 3.
Toute lvalue peut être vue comme une rvalue ; il su±t d'ignorer le contenant (adresse) pour ne voir que le contenu
(type, valeur). La raison principale de la distinction entre lvalue et rvalue est la suivante : seule une lvalue peut
figurer à gauche du signe = dans une affectation ; n'importe quelle rvalue peut apparaitre à droite. Cela justifie les
appellations lvalue (« left value ») et rvalue (« right value »).
Les sections suivantes préciseront, pour chaque sorte d'expression complexe, s'il s'agit ou non d'une lvalue.
Pour les expressions simples, c'est-à-dire les constantes littérales et les identificateurs, la situation est la suivante :
· sont des lvalue :
· les noms des variables simples (nombres et pointeurs),
· les noms des variables d'un type struct ou union
· ne sont pas des lvalue :
· les constantes,
· les noms des variables de type tableau,
· les noms des fonctions.
II-A-2 - Priorité des opérateurs
C comporte de très nombreux opérateurs. La plupart des caractères spéciaux désignent des opérations et subissent
les mêmes règles syntaxiques, notamment pour ce qui concerne le jeu des priorités et la possibilité de parenthésage.
La table 2 montre l'ensemble de tous les opérateurs, classés par ordre de priorité.
Le langage C par Henri Garreta
- 25 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Remarque. Dans certains cas un même signe, comme -, désigne deux opérateurs, l'un
unaire (à un argu- ment), l'autre binaire (à deux arguments). Dans le tableau 2, les su±xes
un et bin précisent de quel opérateur il s'agit.
Le signe -> indique l'associativité « de gauche à droite » ; par exemple, l'expression x - y - z signifie (x - y) - z. Le
signe <- indique l'associativité de « droite à gauche » ; par exemple, l'expression x = y = z signifie x = (y = z).
Notez que le sens de l'associativité des opérateurs précise la signification d'une
expression, mais en aucun cas la chronologie de l'évaluation des sous-expressions.
II-B - Présentation détaillée des opérateurs
II-B-1 - Appel de fonction ()
Opération : application d'une fonction à une liste de valeurs. Format :
exp0 ( exp1 , ... expn )
exp1 , ... expn sont appelés les arguments effectifs de l'appel de la fonction. exp0 doit être de type fonction rendant
une valeur de type T ou bien 12 adresse d'une fonction rendant une valeur de type T. Alors exp0 ( exp1 , ... expn )
possède le type T. La valeur de cette expression découle de la définition de la fonction (à l'intérieur de la fonction,
cette valeur est précisée par une ou plusieurs instructions « return exp ; »).
L'expression exp0 ( exp1 , ... expn ) n'est pas une lvalue.
ExempleLe langage C par Henri Garreta
- 26 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
y = carre(2 * x) + 3;
sachant que carre est le nom d'une fonction rendant un double, l'expression carre(2 * x) a le type double. Un coup
d'¾il au corps de la fonction (cf. section 1.6.2) pourrait nous apprendre que la valeur de cette expression n'est autre
que le carré de son argument, soit ici la valeur 4x2.
Les contraintes supportées par les arguments effectifs ne sont pas les mêmes dans le C ANSI et dans le C original
(ceci est certainement la plus grande différence entre les deux versions du langage) :
A. En C ANSI, si la fonction a été définie ou déclarée avec prototype (cf. section 4.1) :
· le nombre des arguments effectifs doit correspondre à celui des arguments formels 13 ;
· chaque argument effectif doit être compatible, au sens de l'affectation, avec l'argument formel correspondant
;
· la valeur de chaque argument effectif subit éventuellement les mêmes conversions qu'elle subirait dans
l'affectation argument formel = argument effectif
B. En C original, ou en C ANSI si la fonction n'a pas été définie ou déclarée avec prototype :
· aucune contrainte (de nombre ou de type) n'est imposée aux arguments effectifs ;
· tout argument effectif de type char ou short est converti en int ;
· tout argument effectif de type float est converti en double ;
· les autres arguments effectifs ne subissent aucune conversion.
Par exemple, avec la fonction carre définie à la section 1.6.2, l'appel
y = carre(2);
est erroné en C original (la fonction re»coit la représentation interne de la valeur entière 2 en « croyant » que c'est
la représentation interne d'un double) mais il est correct en C ANSI (l'entier 2 est converti en double au moment
de l'appel).
Toutes ces questions sont reprises plus en détail à la section 4.
Remarque 1. Bien noter que les parenthèses doivent apparaitre, même lorsque la liste des
arguments est vide. Leur absence ne provoque pas d'erreur, mais change complètement
le sens de l'expression. Cela constitue un piège assez vicieux tendu aux programmeurs
dont la langue maternelle est Pascal.
Remarque 2. Il faut savoir que lorsqu'une fonction a été déclarée comme ayant des
arguments en nombre variable (cf. section 4.3.4) les arguments correspondant à la partie
variable sont traités comme les arguments des fonctions sans prototype, c'est-à-dire selon
les règles de la section B ci-dessus.
Cette remarque est loin d'être marginale car elle concerne, excusez du peu, les deux fonctions les plus utilisées :
printf et scanf. Ainsi, quand le compilateur rencontre l'instruction
printf(expr0, expr1, ... exprk);
Le langage C par Henri Garreta
- 27 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
il vérifie que expr0 est bien de type char *, mais les autres paramètres effectifs expr1, ... exprk sont traités selon
les règles de la section B.
*
12 Cette double possibilité est commentée à la section 6.3.4.
*
13 Il existe néanmoins un moyen pour écrire des fonctions avec un nombre variable d'arguments (cf. section 4.3.4)
II-B-2 - Indexation []
Définition restreinte (élément d'un tableau). Opération : accès au ieme élément d'un tableau. Format :
exp0 [ exp1 ]
exp0 doit être de type « tableau d'objets de type T », exp1 doit être d'un type entier. Alors exp0[exp1] est de type T
; cette expression désigne l'élément du tableau dont l'indice est donné par la valeur de exp1. Deux détails auxquels
on tient beaucoup en C :
· le premier élément d'un tableau a toujours l'indice 0 ;
· il n'est jamais vérifié que la valeur de l'indice dans une référence à un tableau appartient à l'intervalle 0:::N ¡ 1
déterminé par le nombre N d'éléments alloués par la déclaration du tableau. Autrement dit, il n'y a jamais de «
test de débordement ».
Exemple. L'expression t[0] désigne le premier élément du tableau t, t[1] le second, etc.
En C, les tableaux sont toujours à un seul indice ; mais leurs composantes peuvent être à leur tour des tableaux. Par
exemple, un élément d'une matrice rectangulaire sera noté :
m[i][j]
Une telle expression suppose que m[i] est de type « tableau d'objets de type T » et donc que m est de type « tableau
de tableaux d'objets de type T ». C'est le cas, par exemple, si m a été déclarée par une expression de la forme (NL
et NC sont des constantes) :
double m[NL][NC];
Définition complète (indexation au sens large). Opération : accès à un objet dont l'adresse est donnée par une adresse
de base et un déplacement. Format :
exp0 [ exp1 ]
exp0 doit être de type « adresse d'un objet de type T », exp1 doit être de type entier. Alors exp0[exp1] désigne l'objet
de type T ayant pour adresse (voir la figure 2) :
Le langage C par Henri Garreta
- 28 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Fig. 2 - L'indexation
Il est clair que, si exp0 est de type tableau, les deux définitions de l'indexation données coijncident. Exemple : si t
est un tableau d'entiers et p un « pointeur vers entier » auquel on a affecté l'adresse de t[0], alors les expressions
suivantes désignent le même objet :
t[i] p[i] *(p + i) *(t + i)
Dans un cas comme dans l'autre, l'expression exp0[exp1] est une lvalue, sauf si elle est de type tableau.
II-B-3 - Sélection .
Opération : accès à un champ d'une structure ou d'une union. Format :
exp . identif
exp doit posséder un type struct ou union, et identif doit être le nom d'un des champs de la structure ou de l'union en
question. En outre, exp doit être une lvalue. Alors, exp.identif désigne le champ identif de l'objet désigne par exp.
Cette expression est une lvalue, sauf si elle est de type tableau.
Exemple. Avec la déclaration
struct personne {
long int num;
struct {
char rue[32];
char *ville;
} adresse;
} fiche;
les expressions
fiche.num fiche.adresse fiche.adresse.rue etc.
désignent les divers champs de la variable fiche. Seules les deux premières sont des lvalue.
II-B-4 - Sélection dans un objet pointé ->
Opération : accès au champ d'une structure ou d'une union pointée. Format :
exp->identif
exp doit posséder le type « adresse d'une structure ou d'une union » et identif doit être le nom d'un des champs
de la structure ou de l'union en question. Dans ces conditions, exp->identif désigne le champ identif de la structure
ou de l'union dont l'adresse est indiquée par la valeur de exp. Cette expression est une lvalue, sauf si elle est de
type tableau.
Le langage C par Henri Garreta
- 29 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Ainsi, exp->identif est strictement synonyme de (*exp).identif (remarquez que les parenthèses sont indis- pensables).
Par exemple, avec la déclaration :
struct noeud {
int info;
struct noeud *fils, *frere;
} *ptr;
les expressions suivantes sont correctes :
ptr->info ptr->fils->frere ptr->fils->frere->frere
II-B-5 - Négation !
Opération : négation logique. Format :
!exp
Aucune contrainte. Cette expression désigne une valeur de l'ensemble f0; 1g, définie par :
Cette expression n'est pas une lvalue.
Remarque. Bien que cela ne soit pas exigé par le langage C, on évitera de « nier » (et
plus généralement de comparer à zéro) des expressions d'un type flottant (float, double).
A cause de l'imprécision inhérente à la plupart des calculs avec de tels nombres, l'égalité
à zéro d'un flottant n'est souvent que le fruit du hasard.
II-B-6 - Complément à 1 ~
Opération : négation bit à bit. Format :
~exp
exp doit être d'un type entier. Cette expression désigne l'objet de même type que exp qui a pour codage interne la
configuration de bits obtenue en inversant chaque bit du codage interne de la valeur de exp : 1 devient 0, 0 devient 1.
Cette expression n'est pas une lvalue.
Remarque. Le complément à un n'est pas une opération abstraite (cf. section 2.3.3). La
portabilité d'un programme ou cet opérateur figure n'est donc pas assurée.
II-B-7 - Les célèbres ++ et --
Il existe deux opérateurs unaires ++ différents : l'un est postfixé (écrit derrière l'opérande), l'autre préfixé (écrit devant).
Le langage C par Henri Garreta
- 30 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
1. Opération : post-incrémentation. Format :
exp++
exp doit être de type numérique (entier ou flottant) ou pointeur. Ce doit être une lvalue. Cette expression est
caractérisée par :
· un type : celui de exp ;
· une valeur : la même que exp avant l'évaluation de exp++ ;
· un effet de bord : le même que celui de l'affectation exp = exp + 1.
2. Opération : pré-incrémentation. Format :
++exp
exp doit être de type numérique (entier ou flottant) ou pointeur. Ce doit être une lvalue. Cette expression est
caractérisée par :
· un type : celui de exp ;
· une valeur : la même que exp après l'évaluation de exp++ ;
· un effet de bord : le même que celui de l'affectation exp = exp + 1.
Les expressions exp++ et ++exp ne sont pas des lvalue.
Exemple.
L'affectation équivaut à
y = x++ ; y = x ; x = x + 1 ;
y = ++x ; x = x + 1 ; y = x ;
L'opérateur ++ bénéficie de l'arithmétique des adresses au même titre que +. Ainsi, si exp est de type « pointeur vers
un objet de type T », la quantité effectivement ajoutée à exp par l'expression exp++ dépend de la taille de T.
Il existe de même deux opérateurs unaires -- donnant lieu à des expressions exp-- et --exp. L'explication est la même,
en rempla»cant +1 par ¡1.
Application : réalisation d'une pile. Il est très agréable de constater que les opérateurs ++ et
-- et la manière d'indexer les tableaux en C se combinent harmonieusement et permettent
par exemple la réalisation simple et e±cace de piles par des tableaux, selon le schéma
suivant :
· déclaration et initialisation d'une pile (OBJET est un type prédéfini, dépendant du problème particulier
considéré ; MAXPILE est une constante représentant un majorant du nombre d'éléments dans la pile) :
OBJET espace[MAXPILE];
int nombreElements = 0;
Le langage C par Henri Garreta
- 31 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· opération « empiler la valeur de x » :
if (nombreElements >= MAXPILE)
erreur("tentative d'empilement dans une pile pleine");
espace[nombreElements++] = x;
· opération « dépiler une valeur et la ranger dans x » :
if (nombreElements <= 0)
erreur("tentative de depilement d'une pile vide");
x = espace[--nombreElements];
On notera que, si on procède comme indiqué ci-dessus, la variable nombreElements possède constamment la valeur
que son nom suggère : le nombre d'éléments effectivement présents dans la pile.
II-B-8 - Moins unaire -
Opération : changement de signe. Format :
-exp
exp doit être une expression numérique (entière ou réelle). Cette expression représente l'objet de même type que
exp dont la valeur est l'opposée de celle de exp. Ce n'est pas une lvalue.
II-B-9 - Indirection *
Opération : accès à un objet pointé. On dit aussi « déréference ». Format :
*exp
exp doit être une expression de type « adresse d'un objet de type T ». *exp représente alors l'objet de type T ayant
pour adresse la valeur de exp.
L'expression *exp est une lvalue.
Remarque 1. On prendra garde au fait qu'il existe un bon nombre d'opérateurs ayant une
priorité supérieure à celle de *, ce qui oblige souvent à utiliser des parenthèses. Ainsi par
exemple les expressions Pascal e"[i] et e[i]" doivent respectivement s'écrire, en C, (*e)[i]
et *(e[i]), la deuxième pouvant aussi s'écrire *e[i].
Remarque 2. L'expression *p signifie accès à la mémoire dont p contient l'adresse. C'est
une opération « sans filet ». Quel que soit le contenu de p, la machine pourra toujours le
considérer comme une adresse et accéder à la mémoire correspondante. Il appartient au
programmeur de prouver que cete mémoire a été effectivement allouée au programme
en question. Si c'est le pas on dit que la valeur de p est une adresse valide ; dans le cas
contraire, les pires erreurs sont à craindre à plus ou moins court terme.
Le langage C par Henri Garreta
- 32 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
II-B-10 - Obtention de l'adresse &
Opération : obtention de l'adresse d'un objet occupant un emplacement de la mémoire. Format :
&exp
exp doit être une expression d'un type quelconque T. Ce doit être une lvalue.
L'expression &exp a pour type « adresse d'un objet de type T » et pour valeur l'adresse de l'objet représenté par
exp. L'expression &exp n'est pas une lvalue.
Ainsi, si i est une variable de type int et p une variable de type « pointeur vers un int », alors à la suite de l'instruction
p = &i;
i et *p désignent le même objet.
Exemple 1. Une utilisation fréquente de cet opérateur est l'obtention de l'adresse d'une variable en vue de la passer
à une fonction pour qu'elle modifie la variable :
scanf("%d%lf%s", &i, &t[j], &p->nom);
Exemple 2. Une autre utilisation élégante de cet opérateur est la création de composantes « fixes » dans les structures
chainées. Le programme suivant déclare une liste chainée circulaire représentée par le pointeur entree et réduite
pour commencer à un unique maillon qui est son propre successeur (voir figure 3) ; d'autres maillons seront créés
dynamiquement durant l'exécution :
Fig. 3 - Maillon fixe en tête d'une liste chainée
struct en_tete {
long taille;
struct en_tete *suivant;
} en_tete_fixe = { 0, &en_tete_fixe };
struct en_tete *entree = &en_tete_fixe;
Remarque. Une expression réduite à un nom de tableau, à un nom de fonction ou à une
constante chaine de caractères est considérée comme une constante de type adresse
; l'opérateur & appliqué à de telles expressions est donc sans objet. Un tel emploi de &
devrait être considéré comme erroné, mais beaucoup de compilateurs se contentent de
l'ignorer.
II-B-11 - Opérateur sizeof
Opération : calcul de la taille correspondant à un type. Première forme :
sizeof ( descripteur-de-type )
Le langage C par Henri Garreta
- 33 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Cette expression représente un nombre entier qui exprime la taille qu'occuperait en mémoire un objet possédant le
type indiqué (les descripteurs de types sont expliqués à la section 5.4).
Deuxième forme :
sizeof exp
exp est une expression quelconque (qui n'est pas évaluée par cette évaluation de sizeof). L'expression sizeof
exp représente la taille qu'occuperait en mémoire un objet possédant le même type que exp. Dans un cas comme
dans l'autre sizeof... est une expression constante. En particulier, ce n'est pas une lvalue.
La taille des objets est exprimée en nombre d'octets. Plus exactement, l'unité choisie est telle que la valeur de
sizeof(char) soit 1 (on peut aussi voir cela comme une définition du type char).
Remarque 1. Dans le C original, le type de l'expression sizeof... est int. Dans le C ANSI, ce
type peut changer d'un système à un autre (int, long, unsigned, etc.). Pour cette raison il
est défini, sous l'appellation size t, dans le fichier stddef.h. Il est recommandé de déclarer
de type size t toute variable (resp. toute fonction) devant contenir (resp. devant renvoyer)
des nombres qui représentent des tailles.
Remarque 2. Lorsque son opérande est un tableau (ou une expression de type tableau)
la valeur rendue par sizeof est l'encombrement effectif du tableau. Par exemple, avec la
déclaration
char tampon[80];
l'expression sizeof tampon vaut 80, même si cela parait en contradiction avec le fait que tampon peut être vu comme
de type « adresse d'un char ». Il en découle une propriété bien utile : quel que soit le type du tableau t, la formule
sizeof t / sizeof t[0]
exprime toujours le nombre d'éléments de t.
II-B-12 - Conversion de type (cast" operator)
Opération : conversion du type d'une expression. Format :
( type2 ) exp
type2 représente un descripteur de type ; exp est une expression quelconque. L'expression ci-dessus désigne un
élément de type type2 qui est le résultat de la conversion vers ce type de l'élément représenté par exp.
L'expression (type2)exp n'est pas une lvalue.
Les conversions légitimes (et utiles) sont 14 :
Le langage C par Henri Garreta
- 34 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· Entier vers un entier plus long (ex. char ! int). Le codage de exp est étendu de telle manière que la valeur
représentée soit inchangée.
· Entier vers un entier plus court (ex. long ! short). Si la valeur de exp est assez petite pour être repré- sentable
dans le type de destination, sa valeur est la même après conversion. Sinon, la valeur de exp est purement et
simplement tronquée (une telle troncation, sans signification abstraite, est rarement utile).
· Entier signé vers entier non signé, ou le contraire. C'est une conversion sans travail : le compilateur se borne
à interpréter autrement la valeur de exp, sans effectuer aucune transformation de son codage interne.
· Flottant vers entier : la partie fractionnaire de la valeur de exp est supprimée. Par exemple, le flottant 3.14
devient l'entier 3. Attention, on peut voir cette conversion comme une réalisation de la fonction mathématique
partie entière, mais uniquement pour les nombres positifs : la conversion en entier de -3.14 donne -3, non -4.
· Entier vers flottant. Sauf cas de débordement (le résultat est alors imprévisible), le flottant obtenu est celui qui
approche le mieux l'entier initial. Par exemple, l'entier 123 devient le flottat 123.0.
· Adresse d'un objet de type T1 vers adresse d'un objet de type T2. C'est une conversion sans travail : le
compilateur donne une autre interprétation de la valeur de exp, sans effectuer aucune transformation de son
codage interne.
Danger ! Une telle conversion est entièrement placée sous la responsabilité du
programmeur, le compilateur l'accepte toujours.
· Entier vers adresse d'un objet de type T. C'est encore une conversion sans travail : la valeur de exp est
interprétée comme un pointeur, sans transformation de son codage interne.
Danger ! Une telle conversion est entièrement placée sous la responsabilité du
programmeur. De plus, si la représentation interne de exp n'a pas la taille voulue pour un
pointeur, le résultat est imprévisible.
Toutes les conversions ou l'un des types en présence, ou les deux, sont des types struct
ou union sont interdites.
Note 1. Si type2 et le type de exp sont numériques, alors la conversion effectuée à
l'occasion de l'évaluation de l'expression ( type2 ) exp est la même que celle qui est faite
lors d'une affectation
x = expr;
ou x représente une variable de type type2.
Note 2. En toute rigueur, le fait que l'expression donnée par un opérateur de conversion
de type ne soit pas une lvalue interdit des expressions qui auraient pourtant été pratiques,
comme
((int) x)++; /* DANGER ! */
Cependant, certains compilateurs acceptent cette expression, la traitant comme
x = (le type de x)((int) x + 1) ;
Exemple 1. Si i et j sont deux variables entières, l'expression i / j représente leur division entière (ou euclidienne,
ou encore leur quotient par défaut). Voici deux manières de ranger dans x (une variable de type float) leur quotient
décimal :
Le langage C par Henri Garreta
- 35 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
x = (float) i / j; x = i / (float) j;
Et voici deux manières de se tromper (en n'obtenant que le résultat de la conversion vers le type float de leur division
entière)
x = i / j; /* ERREUR */ x = (float)(i / j); /* ERREUR */
Exemple 2. Une utilisation pointue et dangereuse, mais parfois nécessaire, de l'opérateur de conversion de type entre
types pointeurs consiste à s'en servir pour « voir » un espace mémoire donné par son adresse comme possédant
une certaine structure alors qu'en fait il n'a pas été ainsi déclaré :
· déclaration d'une structure :
struct en_tete {
long taille;
struct en_tete *suivant;
};
· déclaration d'un pointeur « générique » :
void *ptr;
· imaginez que {pour des raisons non détaillées ici{ ptr possède à un endroit donné une valeur qu'il est légitime
de considérer comme l'adresse d'un objet de type struct en tete (alors que ptr n'a pas ce type-là). Voici un
exemple de manipulation cet objet :
((struct en_tete *) ptr)->taille = n;
Bien entendu, une telle conversion de type est faite sous la responsabilité du programmeur, seul capable de garantir
qu'à tel moment de l'exécution du programme ptr pointe bien un objet de type struct en tete.
N.B. L'appel d'une fonction bien écrite ne requiert jamais un « cast ». L'opérateur de change- ment de type est parfois
nécessaire, mais son utilisation diminue toujours la qualité du programme qui l'emploie, pour une raison facile à
comprendre : cet opérateur fait taire le compilateur. En effet, si expr est d'un type poin- teur et type2 est un autre
type pointeur, l'expression (type2)expr est toujours acceptée par le compilateur sans le moindre avertissement. C'est
donc une manière de cacher des erreurs sans les résoudre.
Exemple typique : si on a oublié de mettre en tête du programme la directive #include <stdlib.h>, l'utilisation de la
fonction malloc de la bibliothèque standard soulève des critiques :
...
MACHIN *p;
...
p = malloc(sizeof(MACHIN));
...
A la compilation on a des avertissements. Par exemple (avec gcc) :
Le langage C par Henri Garreta
- 36 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
monProgramme.c: In function `main'
monProgramme.c:9:warning: assignment makes pointer from integer without a cast
On croit résoudre le problème en utilisant l'opérateur de changement de type
...
p = (MACHIN *) malloc(sizeof(MACHIN)); /* CECI NE REGLE RIEN! */
...
et il est vrai que la compilation a lieu maintenant en silence, mais le problème n'est pas résolu, il est seulement caché.
Sur un ordinateur ou les entiers et les pointeurs ont la même taille cela peut marcher, mais ailleurs la valeur rendue
par malloc sera endommagée lors de son affectation à p. A ce sujet, voyez la remarque de la page 65.
Il su±t pourtant de bien lire l'avertissement a±ché par le compilateur pour trouver la solution. L'affectation p =
malloc(...) lui fait dire qu'on fabrique un pointeur (p) à partir d'un entier (le résultat de malloc) sans opérateur cast. Ce
qui est anormal n'est pas l'absence de cast, mais le fait que le résultat de malloc soit tenu pour un entier 15, et il n'y
a aucun moyen de rendre cette affectation juste aussi longtemps que le compilateur fera une telle hypothèse fausse.
La solution est donc d'informer ce dernier à propos du type de malloc, en faisant précéder l'affectation litigieuse soit
d'une déclaration de cette fonction
void *malloc (size_t);
soit, c'est mieux, d'une directive ad hoc :
#include <stdlib.h>
...
p = malloc(sizeof(MACHIN));
...
*
14 Attention, il y a quelque chose de trompeur dans la phrase « conversion de exp ». N'oubliez pas que, contrairement
à ce que suggère une certaine manière de parler, l'évaluation de l'expression (type)exp ne change ni le type ni la
valeur de exp.
*
15 Rappelez-vous que toute fonction appelée et non déclarée est supposée de type int.
II-B-13 - Opérateurs arithmétiques
Ce sont les opérations arithmétiques classiques : addition, soustraction, multiplication, division et modulo (reste de
la division entière). Format :
Aucune de ces expressions n'est une lvalue.
Avant l'évaluation de l'expression, les opérandes subissent les conversions « usuelles » (cf. section 2.3.1). Le langage
C supporte l'arithmétique des adresses (cf. section 6.2.1).
Le langage C par Henri Garreta
- 37 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
A propos de la division. En C on note par le même signe la division entière, qui prend deux opérandes entiers (short,
int, long, etc.) et donne un résultat entier, et la division flottante dans laquelle le résultat est flottant (float, double).
D'autre part, les règles qui commandent les types des opérandes et du résultat des expressions arithmétiques (cf.
section 2.3.1), et notamment la règle dite « du plus fort », ont la conséquence importante suivante : dans l'expression
expr1 / expr2
· si expr1 et expr2 sont toutes deux entières alors « / » est traduit par l'opération « division entière » 16, et le
résultat est entier,
· si au moins une des expressions expr1 ou expr2 n'est pas entière, alors l'opération faite est la division
flottante des valeurs de expr1 et expr2 toutes deux converties dans le type double. Le résultat est une valeur
double qui approche le rationnel expr1
expr2
du mieux que le permet la la précision du type double. Il résulte de cette règle un piège auquel il faut faire attention
: 1/2 ne vaut pas 0:5 mais 0. De même, dans
int somme, nombre;
float moyenne;
...
moyenne = somme / 100;
la valeur de moyenne n'est pas ce que ce nom suggère, car somme et 100 sont tous deux entiers et l'expression
somme / 100 est entière, donc tronquée (et il ne faut pas croire que l'affectation ultérieure à la variable flottante
moyenne pourra retrouver les décimales perdues). Dans ce cas particulier, la solution est simple :
moyenne = somme / 100.0;
Même problème si le dénominateur avait été une variable entière, comme dans
moyenne = somme / nombre;
ici la solution est un peu plus compliquée :
moyenne = somme / (double) nombre;
*
16On prendra garde au fait que si les opérandes ne sont pas tous deux positifs la division entière du langage C ne
coincide pas avec le « quotient par défaut » (quotient de la « division euclidienne » des matheux). Ici, si q est le
quotient de a par b alors jqj est le quotient de jaj par jbj. Par exemple, la valeur de (¡17)=5 ou de 17=(¡5) est ¡3 alors
que le quotient par défaut de ¡17 par 5 est plutot ¡4 (¡17 = 5 £ (¡4) + 3).
II-B-14 - Décalages << >>
Opération : décalages de bits. Format :
Le langage C par Henri Garreta
- 38 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
exp1 et exp2 doivent être d'un type entier (char, short, long, int...). L'expression exp1 << exp2 (resp. exp1 >> exp2)
représente l'objet de même type que exp1 dont la représentation interne est obtenue en décalant les bits de la
représentation interne de exp1 vers la gauche 17 (resp. vers la droite) d'un nombre de positions égal à la valeur de
exp2. Autrement dit,
exp1 << exp2
est la même chose (si << 1 apparait exp2 fois) que
((exp1 << 1) << 1) ... << 1
Remarque analogue pour le décalage à droite >>.
Les bits sortants sont perdus. Les bits entrants sont :
· dans le cas du décalage à gauche, des zéros ;
· dans le cas du décalage à droite : si exp1 est d'un type non signé, des zéros ; si exp1 est d'un type signé, des
copies du bit de signe 18.
Par exemple, si on suppose que la valeur de exp1 est codée sur huit bits, notés
b7b6b5b4b3b2b1b0
(chaque bi vaut 0 ou 1), alors
exp1 << 1 = b6b5b4b3b2b1b00
exp1 >> 1 = 0b7b6b5b4b3b2b1 (cas non signé)
exp1 >> 1 = b7b7b6b5b4b3b2b1 (cas signé)
Avant l'évaluation du résultat, les opérandes subissent les conversions usuelles (cf. section 2.3.1). Ces ex- pressions
ne sont pas des lvalue.
Remarque. Les opérateurs de décalage de bits ne sont pas des opérations abstraites (cf.
section 2.3.3). La portabilité d'un programme ou ils figurent n'est donc pas assurée.
*
17Par convention la droite d'un nombre codé en binaire est le coté des bits de poids faible (les unités) tandis que la
gauche est celui des bits de poids forts (correspondant aux puissances 2k avec k grand).
*
18De cette manière le décalage à gauche correspond (sauf débordement) à la multiplication par 2exp2 , tandis que le
décalage à droite correspond à la division entière par 2exp2 , aussi bien si exp1 est signée que si elle est non signée
II-B-15 - Comparaisons == != < <= > >=
Il s'agit des comparaisons usuelles : égal, différent, inférieur, inférieur ou égal, supérieur, supérieur ou égal. Format :
Le langage C par Henri Garreta
- 39 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
exp1 et exp2 doivent être d'un type simple (nombre ou pointeur). Cette expression représente l'un des éléments (de
type int) 1 ou 0 selon que la relation indiquée est ou non vérifiée par les valeurs de exp1 et exp2. Avant la prise
en compte de l'opérateur, les opérandes subissent les conversions usuelles (cf. section 2.3.1). Ces expressions ne
sont pas des lvalue.
Notez que, contrairement à ce qui se passe en Pascal, ces opérateurs ont une priorité supérieure à celle des
connecteurs logiques, ce qui évite beaucoup de parenthèses disgracieuses. Ainsi, en C, l'expression
0 <= x && x < 10
est correctement écrite.
A propos de « être vrai » et « être non nul ». Comme on a dit (cf. section 1.4.1), le type booléen n'existe pas en C.
N'importe quelle expression peut occuper la place d'une condition ; elle sera tenue pour fausse si elle est nulle, pour
vraie dans tous les autres cas. Une conséquence de ce fait est la suivante : à la place de
if (i != 0) etc.
while (*ptchar != '�') etc.
for (p = liste; p != NULL; p = p->suiv) etc.
on peut écrire respectivement
if (i) etc.
while (*ptchar) etc.
for (p = liste; p; p = p->suiv) etc.
On admet généralement qu'à cause de propriétés techniques des processeurs, ces expressions raccourcies
représentent une certaine optimisation des programmes. C'est pourquoi les premiers programmeurs C, qui
ne disposaient que de compilateurs simples, ont pris l'habitude de les utiliser largement. On ne peut plus
aujour- d'hui conseiller cette pratique. En effet, les compilateurs actuels sont su±samment perfectionnés pour
effectuer spontanément cette sorte d'optimisations et il n'y a plus aucune justification de l'emploi de ces comparaisons
implicites qui rendent les programmes bien moins expressifs.
Attention. La relation d'égalité se note ==, non =. Voici une faute possible chez les
nouveaux venus à C en provenance de Pascal qui, pour traduire le bout de Pascal if a
= 0 then etc., écrivent
if (a = 0) etc.
Du point de vue syntaxique cette construction est correcte, mais elle est loin d'avoir l'effet escompté. En tant
qu'expression, a = 0 vaut 0 (avec, comme effet de bord, la mise à zéro de a). Bien s^ur, il fallait écrire
if (a == 0) etc.
II-B-16 - Opérateurs de bits & j ^
Le langage C par Henri Garreta
- 40 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.
_______________________________________
Voici un exemple : le prédicat qui caractérise la présence d'un élément dans une liste chainée. Version (récursive)
habituelle :
int present(INFO x, LISTE L) { /* l'information x est-elle dans la liste L ? */
if (L == NULL)
return 0;
else if (L->info == x)
return 1;
else
return present(x, L->suivant);
}
Version dans un style fonctionnel, permise par la sémantique des opérateurs && et || :
int existe(INFO x, LISTE L) { /* l'information x est-elle dans la liste L ? */
return L != NULL && (x == L->info || existe(x, L->suivant));
}
II-B-18 - Expression conditionnelle ? :
Opération : sorte de if...then...else... présenté sous forme d'expression, c'est-à-dire renvoyant une valeur.
Format :
exp0 ? exp1 : exp2
exp0 est d'un type quelconque. exp1 et exp2 doivent être de types compatibles. Cette expression est évaluée de
la manière suivante :
La condition exp0 est évaluée d'abord
· si sa valeur est non nulle, exp1 est évaluée et définit la valeur de l'expression conditionnelle. Dans ce cas,
exp2 n'est pas évaluée ;
· sinon, exp2 est évaluée et définit la valeur de l'expression conditionnelle. Dans ce cas, exp1 n'est pas
évaluée.
L'expression exp0 ?exp1:exp2 n'est pas une lvalue.
Exemple. L'opérateur conditionnel n'est pas forcément plus facile à lire que l'instruction conditionnelle, mais permet
quelquefois de réels allégements du code. Imaginons un programme devant a±cher un des textes non ou oui selon
que la valeur d'une variable reponse est nulle ou non. Solutions classiques de ce micro-problème :
if (reponse)
printf("la réponse est oui");
else
printf("la réponse est non");
ou bien, avec une variable auxiliaire :
char *texte;
...
Le langage C par Henri Garreta
- 43 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
if (reponse)
texte = "oui";
else
texte = "non";
printf("la réponse est %s", texte);
Avec l'opérateur conditionnel c'est bien plus compact :
printf("la réponse est %s", reponse ? "oui" : "non");
II-B-19 - Affectation =
Opération : affectation, considérée comme une expression. Format :
exp1 = exp2
exp1 doit être une lvalue. Soit type1 le type de exp1 ; l'affectation ci-dessus représente le même objet que
( type1 ) exp2
(la valeur de exp2 convertie dans le type de exp1), avec pour effet de bord le rangement de cette valeur dans
l'emplacement de la mémoire déterminé par exp1.
L'expression exp1 = exp2 n'est pas une lvalue.
Contrairement à ce qui se passe dans d'autres langages, une affectation est donc considérée en C comme une
expression : elle « fait » quelque chose, mais aussi elle « vaut » une certaine valeur et, à ce titre, elle peut figurer
comme opérande dans une sur-expression. On en déduit la possibilité des affectations multiples, comme dans
l'expression :
a = b = c = 0;
comprise comme a = (b = (c = 0)). Elle aura donc le même effet que les trois affectations a = 0 ; b = 0 ;
c = 0 ; Autre exemple, lecture et traitement d'une suite de caractères dont la fin est indiquée par un point :
...
while ((c = getchar()) != '.')
exploitation de c
...
Des contraintes pèsent sur les types des deux opérandes d'une affectation exp1 = exp2. Lorsqu'elles sont satisfaites
on dit que exp1 et exp2 sont compatibles pour l'affectation. Essentiellement :
· deux types numériques sont toujours compatibles pour l'affectation. La valeur de exp2 subit éventuellement
une conversion avant d'être rangée dans exp1. La manière de faire cette conversion est la même que dans le
cas de l'opérateur de conversion (cf. section 2.2.12) ;
Le langage C par Henri Garreta
- 44 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· si exp1 et exp2 sont de types adresses distincts, certains compilateurs (dont ceux conformes à la norme
ANSI) les considéreront comme incompatibles tandis que d'autres compilateurs se limiteront à donner un
message d'avertissement lors de l'affectation de exp2 à exp1 ;
· dans les autres cas, exp1 et exp2 sont compatibles pour l'affectation si et seulement si elles sont de même
type.
D'autre part, de la signification du nom d'une variable de type tableau (cf. 5.1.1) et de celle d'une variable de type
structure (cf. 5.2.1) on déduit que :
· on ne peut affecter un tableau à un autre, même s'ils sont définis de manière rigoureusement identique (un
nom de tableau n'est pas une lvalue) ;
· on peut affecter le contenu d'une variable de type structure ou union à une autre, à la condition qu'elles aient
été explicitement déclarées comme ayant exactement le même type.
II-B-20 - Autres opérateurs d'affectation += *= etc.
Opération binaire vue comme une modification du premier opérande. Format :
exp1 doit être une lvalue. Cela fonctionne de la manière suivante : si ² représente l'un des opérateurs + - * / % >>
<< & ^ j, alors
exp1 ² = exp2
peut être vue comme ayant la même valeur et le même effet que
exp1 = exp1 ² exp2
mais avec une seule évaluation de exp1. L'expression résultante n'est pas une lvalue.
Exemple. écrivons la version itérative usuelle de la fonction qui calcule xn avec x flottant et n entier non négatif :
double puissance(double x, int n) {
double p = 1;
while (n != 0) {
if (n % 2 != 0) /* n est-il impair ? */
p *= x;
x *= x;
n /= 2;
}
return p;
}
Remarque. En examinant ce programme, on peut faire les mêmes commentaires qu'à
l'occasion de plusieurs autres éléments du langage C :
· l'emploi de ces opérateurs constitue une certaine optimisation du programme. En langage machine, la suite
d'instructions qui traduit textuellement a += c est plus courte que celle qui correspond à a = b + c. Or, un
compilateur rustique traitera a = a + c comme un cas particulier de a = b + c, sans voir l'équivalence avec la
première forme.
Le langage C par Henri Garreta
- 45 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· hélas, l'emploi de ces opérateurs rend les programmes plus denses et moins faciles à lire, ce qui favorise
l'apparition d'erreurs de programmation.
· de nos jours les compilateurs de C sont devenus assez perfectionnés pour déceler automatiquement la
possibilité de telles optimisations. Par conséquent, l'argument de l'e±cacité ne justifie plus qu'on obscurcisse
un programme par l'emploi de tels opérateurs.
Il faut savoir cependant qu'il existe des situations ou l'emploi de ces opérateurs n'est pas qu'une question d'e±cacité.
En effet, si exp1 est une expression sans effet de bord, alors les expressions exp1 ² = exp2 et exp1 = exp1 ² exp2
sont réellement équivalentes. Mais ce n'est plus le cas si exp1 a un effet de bord. Il est clair, par exemple, que les
deux expressions suivantes ne sont pas équivalentes (la première est tout simplement erronée) 20 :
nombre[rand() % 100] = nombre[rand() % 100] + 1; /* ERRONE ! */
nombre[rand() % 100] += 1; /* CORRECT */
II-B-21 - L'opérateur virgule ,
Opération : évaluation en séquence. Format :
exp1 , exp2
exp1 et exp2 sont quelconques. L'évaluation de exp1 , exp2 consiste en l'évaluation de exp1 suivie de l'évaluation
de exp2. L'expression exp1 , exp2 possède le type et la valeur de exp2 ; le résultat de l'évaluation de exp1 est «
oublié », mais non son éventuel effet de bord (cet opérateur n'est utile que si exp1 a un effet de bord). L'expression
exp1 , exp2 n'est pas une lvalue.
Exemple. Un cas fréquent d'utilisation de cet opérateur concerne la boucle for (cf. section 3.2.6), dont la syntaxe
requiert exactement trois expressions à trois endroits bien précis. Parfois, certaines de ces expressions doivent être
doublées :
...
for (pr = NULL, p = liste; p != NULL; pr = p, p = p->suivant)
if (p->valeur == x)
break;
...
Remarque syntaxique. Dans des contextes ou des virgules apparaissent normalement,
par exemple lors d'un appel de fonction, des parenthèses sont requises afin de forcer le
compilateur à reconnaitre l'opérateur virgule. Par exemple, l'expression
uneFonction(exp1, (exp2, exp3), exp4);
représente un appel de uneFonction avec trois arguments effectifs : les valeurs de exp1, exp3 et exp4. Au passage,
l'expression exp2 aura été évaluée. Il est certain que exp2 aura été évaluée avant exp3, mais les spécifications du
langage ne permettent pas de placer les évaluations de exp1 et exp4 par rapport à celles de exp2 et exp3.
*
20 La fonction rand() renvoie un entier aléatoire distinct chaque fois qu'elle est appelée.
Le langage C par Henri Garreta
- 46 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
II-C - Autres remarques
II-C-1 - Les conversions usuelles
Les règles suivantes s'appliquent aux expressions construites à l'aide d'un des opérateurs *, /, %, +, -, <, <=, >, >=,
==, !=, &, ^, |, && et ||. Mutatis mutandis, elles s'appliquent aussi à celles construites avec les opérateurs *=, /=, %=,
+=, -=, <<=, >>=, &=, ^= et |=.
Dans ces expressions, les opérandes subissent certaines conversions avant que l'expression ne soit évaluée. En C
ANSI ces conversions, dans l' ordre « logique » ou elles sont faites, sont les suivantes :
· Si un des opérandes est de type long double, convertir l'autre dans le type long double ; le type de
l'expression sera long double.
· Sinon, si un des opérandes est de type double, convertir l'autre dans le type double ; le type de l'expression
sera double.
· Sinon, si un des opérandes est de type float, convertir l'autre dans le type float ; le type de l'expression sera
float 21.
· Effectuer la promotion entière : convertir les char, les short, les énumérations et les champs de bits en
des int. Si l'une des valeurs ne peut pas être représentée dans le type int, les convertir toutes dans le type
unsigned int.
· Ensuite, si un des opérandes est unsigned long, convertir l'autre en unsigned long ; le type de l'expres- sion
sera unsigned long.
· Sinon, si un des opérandes est long et l'autre unsigned int 22 :
· si un long peut représenter toutes les valeurs unsigned int, alors convertir l'opérande de type unsigned
int en long. Le type de l'expression sera long ;
· sinon, convertir les deux opérandes en unsigned long. Le type de l'expression sera unsigned long.
· Sinon, si un des opérandes est de type long, convertir l'autre en long ; le type de l'expression sera long.
· Sinon, si un des opérandes est de type unsigned int, convertir l'autre en unsigned int ; le type de l'expression
sera unsigned int.
· Sinon, et si l'expression est correcte, c'est que les deux opérandes sont de type int ; le type de l'expression
sera int.
*
21 Cette règle est apparue avec le C ANSI : le compilateur accepte de faire des calculs sur des °ottants en simple
précision. Dans le C original, elle s'énonce plus simplement : « sinon, si l'un des opérandes est de type float, convertir
les deux opérandes dans le type double ; le type de l'expression sera double ».
*
22 Cette règle compliquée est apparue avec le C ANSI. En C original, le type unsigned « tire vers lui » les autres types.
II-C-2 - L'ordre d'évaluation des expressions
Les seules expressions pour lesquelles l'ordre (chronologique) d'évaluation des opérandes est spécifié sont les
suivantes :
· « exp1 && exp2 » et « exp1 || exp2 » : exp1 est évaluée d'abord ; exp2 n'est évaluée que si la valeur de exp1
ne permet pas de conclure ;
· « exp0 ? exp1 : exp2 » exp0 est évaluée d'abord. Une seule des expressions exp1 ou exp2 est évaluée
ensuite ;
Le langage C par Henri Garreta
- 47 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· « exp0 , exp0 » : exp1 est évaluée d'abord, exp2 est évaluée ensuite.
Dans tous les autres cas, C ne garantit pas l'ordre chronologique dans lequel les opérandes intervenant dans une
expression ou les arguments effectifs d'un appel de fonction sont évalués ; il ne faut donc pas faire d'hypothèse à ce
sujet. La question ne se pose que lorsque ces opérandes et arguments sont à leur tour des expressions complexes
; elle est importante dans la mesure ou C favorise la programmation avec des effets de bord. Par exemple, si i vaut
1, l'expression a[i] + b[i++] peut aussi bien additionner a[1] et b[1] que a[2] et b[1].
L'ordre d'évaluation des opérandes d'une affectation n'est pas fixé non plus. Pour évaluer exp1 = exp2, on peut
évaluer d'abord l'adresse de exp1 et ensuite la valeur de exp2, ou bien faire l'inverse. Ainsi, le résultat de l'affectation
a[i] = b[i++] est imprévisible.
II-C-3 - Les opérations non abstraites
Beaucoup d'opérateurs étudiés dans cette section (opérateurs arithmétiques, comparaisons, logiques, etc.)
représentent des opérations abstraites, c'est-à-dire possédant une définition formelle qui ne fait pas intervenir les
particularités de l'implantation du langage. Bien s^ur, les opérandes sont représentés dans la machine par des
configurations de bits, mais seule leur interprétation comme des entités de niveau supérieur (nombres entiers,
flottants...) est utile pour définir l'effet de l'opération en question ou les contraintes qu'elle subit.
A l'opposé, un petit nombre d'opérateurs, le complément à un (~), les décalages (<< et >>) et les opérations bit-à-bit
(&, ^ et |), n'ont pas forcément de signification abstraite. Les transformations qu'ils effectuent sont définies au niveau
des bits constituant le codage des opérandes, non au niveau des nombres que ces opérandes représentent. De telles
opérations sont réservées aux programmes qui remplissent des fonctions de très bas niveau, c'est-à-dire qui sont
aux points de contact entre la composante logicielle et la composante matérielle d'un système informatique.
L'aspect de cette question qui nous intéresse le plus ici est celui-ci : la portabilité des programmes contenant des
opérations non abstraites n'est pas assurée. Ce défaut, qui n'est pas rédhibitoire dans l'écriture de fonctions de bas
niveau (ces fonctions ne sont pas destinées à être portées), doit rendre le programmeur très précautionneux dès qu'il
s'agit d'utiliser ces opérateurs dans des programmes de niveau supérieur, et le pousser à :
· isoler les opérations non abstraites dans des fonctions de petite taille bien repérées ;
· documenter soigneusement ces fonctions ;
· constituer des jeux de tests validant chacune de ces fonctions
__________________________________________
III - Instructions
III-A - Syntaxe
Dans les descriptions syntaxiques suivantes le su±xe « opt » indique que la formule qu'il qualifie est option- nelle.
Une formule avec des points de suspension, comme « élément ... élément », indique un élément pouvant apparaitre
un nombre quelconque, éventuellement nul, de fois.
instruction
! instruction-bloc
! instruction-expression
! instruction-goto
! instruction-if
! instruction-while
! instruction-do
! instruction-for
! instruction-break
! instruction-continue
! instruction-switch
! instruction-return
! instruction-vide
! identificateur : instruction
instruction-bloc
! f déclaration ... déclaration
instruction-expression
! expression ;
instruction-goto
! goto identif ;
instruction-if
! if ( expression ) instruction else instruction
! if ( expression ) instruction
instruction-while
! while ( expression ) instruction
instruction-do
! do instuction while ( expression ) ;
instruction-for
! for ( expressionopt ; expressionopt ; expressionopt ) instruction
instruction-break
! break ;
instruction-continue
! continue ;
instruction-switch
! switch ( expression ) f instruction-ou-case ... instruction-ou-case g
instruction-ou-case
! case expression-constante : instructionopt
! default : instruction
! instruction
instruction-return
! return expressionopt ;
instruction-vide
Le langage C par Henri Garreta
- 49 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
! ;
C et le point-virgule. Comme l'indique la syntaxe de l'instruction-bloc, en C le point-virgule n'est pas un séparateur
d'instructions mais un terminateur de certaines instructions. Autrement dit, il appartient à la syntaxe de chaque
instruction de préciser si elle doit ou non être terminée par un point-virgule, indépendamment de ce par quoi
l'instruction est suivie dans le programme.
L'oubli du point-virgule à la fin d'une instruction qui en requiert un est toujours une erreur, quelle que soit la situation
de cette instruction. Un surnombre de points-virgules crée des instructions vides.
III-B - Présentation détaillée des instructions
III-B-1 - Blocs
Un bloc est une suite de déclarations et d'instructions encadrée par les deux accolades f et g. Du point de vue de
la syntaxe il se comporte comme une instruction unique et peut figurer en tout endroit ou une instruction simple est
permise.
Le bloc le plus extérieur d'une fonction et les autres blocs plus profonds ont le même statut. En particulier, quelle que
soit sa position dans le programme, un bloc peut comporter ses propres déclarations de variables. Sauf si elle est
déclarée extern, une variable définie dans un bloc est locale à ce bloc et donc inconnue à l'extérieur. Dans le bloc ou
elle est définie, une telle variable masque, sans le détruire, tout autre objet de même nom connu à l'extérieur du bloc.
Sauf si elles sont qualifiées static (ou extern), de telles variables sont créées et éventuellement initialisées lors de
l'activation du bloc ; elles sont détruites dès que le controle quitte le bloc. Il ne faut donc pas espérer qu'une telle
variable conserve sa valeur entre deux passages dans le bloc. Les variables locales aux blocs permettent d'optimiser
la gestion de l'espace local. Par exemple, dans un programme tel que
if (...) {
type1 n;
...
}
else {
type2 x;
...
}
les variables n et x n'existeront jamais simultanément ; le compilateur peut donc leur allouer le même emplace- ment
de la mémoire.
III-B-2 - Instruction-expression
Format :
expression ;
Mais oui, il su±t d'écrire un point-virgule derrière n'importe quelle expression pour en faire une instruction.
Exemples :
Le langage C par Henri Garreta
- 50 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
123; a
i++; b
x = 2 * x + 3; c
printf("%dn", n); d
Intuitivement, une instruction-expression représente l'ordre « évaluez cette expression, ensuite oubliez le résultat ».
Il est clair que si l'expression n'a pas d'effet de bord, cela n'aura servi à rien (exemple a). L'aspect utile de cette notion
est : toute expression avec effet de bord pourra être évaluée uniquement pour son effet de bord (exemple b).
Avec deux cas particuliers très intéressants :
· puisque l'affectation est une expression, on retrouve bien l'instruction d'affectation, fondamentale dans tous
les langages de programmation (exemple c) ;
· toute fonction peut être appelée « comme une procédure » (exemple 23 d), c'est-à-dire en ignorant la valeur
qu'elle rend.
Remarque. Une conséquence regrettable de tout cela est un piège assez vicieux tendu
aux pascaliens.
Imaginons que lirecaractere soit le nom d'une fonction sans argument. L'appel correct de
cette fonction s'écrit :
lirecaractere();
Cependant, puisque le nom d'une fonction est une expression (une constante valant l'adresse de la fonctio) et que
toute expression suivie d'un point-virgule est une instruction correcte, l'énoncé
lirecaractere;
sera trouvé légal par le compilateur. Or cette expression (tout à fait analogue à l'exemple a ci-dessus) ne produit pas
l'appel de la fonction et ne traduit donc probablement pas la pensée du programmeur.
*
23On verra le moment venu (cf. section 7.2.5 d) que printf rend un résultat parfois utile.
III-B-3 - Etiquettes et instruction goto
Format :
étiquette : instruction
Une étiquette est un identificateur ; elle doit être placée devant une fonction, séparée de celle-ci par un caractère
deux points. Elle n'a pas à faire l'objet d'une déclaration explicite ; il su±t de l'écrire devant une instruction pour qu'elle
soit automatiquement connue comme un nom à portée locale. Elle est alors utilisable partout dans la fonction ou elle
apparait (avant et après l'instruction qu'elle préfixe) et elle reste inconnue en dehors de la fonction.
L'instruction
goto étiquette ;
Le langage C par Henri Garreta
- 51 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
transfère le controle à l'instruction préfixée par l'étiquette en question.
Théoriquement, tout algorithme peut être programmé sans utiliser l'instruction goto. Dans certains langages comme
Pascal, elle est utilisée pour obtenir l'abandon d'une structure de controle (exemple : une boucle) depuis l'intérieur
de la structure. Un tel emploi de goto est avantageusement remplacé en C par l'utilisation des instructions return,
break et continue.
Il est donc rare que l'on ait besoin de l'instruction goto en C. Elle ne se révèle utile que lorsqu'il faut abandonner
plusieurs structures de controle (if, while, for...) imbriquées les unes dans les autres. Exemple :
for (i = 0; i < N1; i++) {
for (j = 0; j <= N2; j++)
for (k = 0; k <= N2; k++) {
...
if (...)
goto grande_boucle;
...
}
...
grande_boucle: /* ici on a quitté les deux boucles internes (sur j et k) */
... /* mais on est toujours dans la boucle la plus externe (sur i) */
}
III-B-4 - Instruction if...else...
Formats :
if (expression)
instruction1
else
instruction2
et
if (expression)
instruction1
Dans la première forme, expression est évaluée : si elle est vraie (i.e. non nulle) instruction1 est exécutée ; si elle est
fausse (nulle) instruction2 est exécutée. Dans la deuxième forme, expression est évaluée : si elle est vraie instruction1
est exécutée ; sinon, rien n'est exécuté.
On notera que l'expression conditionnelle doit figurer entre parenthèses. Celles-ci font partie de la syntaxe du if, non
de celle de l'expression.
Lorsque plusieurs instructions if sont imbriquées, il est convenu que chaque else se rapporte au dernier if pour lequel
le compilateur a rencontré une condition suivie d'exactement une instruction. Le listing du programme peut (et doit !)
traduire cela par une indentation (marge blanche) expressive, mais il ne faut pas oublier que le compilateur ne tient
pas compte des marges. Par exemple, le programme suivant est probablement incorrect ; en tout cas, son indentation
ne traduit pas convenablement les rapports entre les if et les else :
if (nombrePersonnes != 0)
if (nombrePersonnes != nombreAdultes)
printf("Il y a des enfants!");
else
Le langage C par Henri Garreta
- 52 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
printf("Il n'y a personne!");
Ce problème se pose dans tous les langages qui offrent deux variétés d'instruction conditionnelle. On le résout soit
par l'utilisation d'instructions vides :
if (nombrePersonnes != 0)
if (nombrePersonnes != nombreAdultes)
printf("Il y a des enfants!");
else
;
else
printf("Il n'y a personne!");
soit, plus simplement, en utilisant des blocs :
if (nombrePersonnes != 0) {
if (nombrePersonnes != nombreAdultes)
printf("Il y a des enfants!");
}
else
printf("Il n'y a personne!");
Remarque. La syntaxe prévoit exactement une instruction entre la condition et le else. Par
conséquent, un excès de points-virgules à la suite de instruction1 constitue une erreur.
Voici une faute qu'on peut faire quand on débute :
if (nombrePersonnes != 0) {
if (nombrePersonnes != nombreAdultes)
printf("Il y a des enfants!");
};
else
printf("Il n'y a personne!");
Il y a maintenant deux instructions entre la ligne du if et celle du else : une instruction-bloc f ... g et une instruction
vide « ; ». Le compilateur signalera donc une erreur sur le else.
III-B-5 - Instructions while et do...while
Ces instructions correspondent respectivement aux instructions while...do... et repeat...until... du langage Pascal.
Notez que la syntaxe exige que la condition figure entre parenthèses. Formats :
while (expression)
instruction
et
do
instruction
while (expression);
Le fonctionnement de ces instructions est décrit par les organigrammes de la figure 4. Fondamentalement, il s'agit de
réitérer l'exécution d'une certaine instruction tant qu'une certaine instruction, vue comme une condition, reste vraie.
Le langage C par Henri Garreta
- 53 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Dans la structure while on vérifie la condition avant d'exécuter l'instruction, tandis que dans la structure do...while
on la vérifie après.
Fig. 4 - Instruction while (à gauche) et do...while (à droite)
L'instruction do...while... exécute donc au moins une fois l'instruction qui constitue son corps avant d'évaluer la
condition de continuation. C'est en cela qu'elle est l'analogue de l'instruction repeat...until du Pascal. Mais on notera
que la condition figurant dans une instruction do...while (« faire tant que... ») et celle qui figurerait dans une instruction
repeat...until équivalente (« répéter jusqu'à ce que... ») sont inverses l'une de l'autre.
III-B-6 - Instruction for
Format :
for ( expr1 ; expr2 ; expr3 )
instruction
Par définition, cette construction équivaut strictement à celle-ci :
expr1 ;
while (expr2) {
instruction
expr3;
}
Ainsi, dans la construction for(expr1 ; expr2 ; expr3) :
· expr1 est l'expression qui effectue les initialisations nécessaires avant l'entrée dans la boucle ;
· expr2 est le test de continuation de la boucle ; il est évalué avant l'exécution du corps de la boucle ;
· expr3 est une expression (par exemple une incrémentation) évaluée à la fin du corps de la boucle.
Par exemple, l'instruction Pascal :
for i:=0 to 9 do
t[i]:=0
se traduit tout naturellement en C
for (i = 0; i < 10; i++)
t[i] = 0;
Les expressions expr1 et expr3 peuvent être absentes (les points-virgules doivent cependant apparaitre). Par
exemple
for ( ; expr2 ; )
instruction
équivaut à
Le langage C par Henri Garreta
- 54 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
while (expr2)
instruction
La condition de continuation expr2 peut elle aussi être absente. On considère alors qu'elle est toujours vraie. Ainsi,
la boucle « indéfinie » peut se programmer :
for ( ; ; )
instruction
Bien entendu, il faut dans ce cas que l'abandon de la boucle soit programmé à l'intérieur de son corps (sinon cette
boucle indéfinie devient infinie !). Exemple :
for (;;) {
printf("donne un nombre (0 pour sortir): ");
scanf("%d", &n);
if (n == 0)
break;
...
exploitation de la donnée n
...
}
Lorsque l'abandon de la boucle correspond aussi à l'abandon de la procédure courante, break est avanta- geusement
remplacée par return comme dans :
char *adresse_de_fin(char *ch) {
/* renvoie adresse du caractère qui suit la chaine ch */
for (;;)
if (*ch++ == 0)
return ch;
}
III-B-7 - Instruction switch
Format :
switch ( expression )
corps
Le corps de l'instruction switch prend la forme d'un bloc f...g renfermant une suite d'instructions entre lesquelles se
trouvent des constructions de la forme
case expression-constante :
ou bien
default :
Le fonctionnement de cette instruction est le suivant : expression est évaluée ;
Le langage C par Henri Garreta
- 55 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
· s'il existe un énoncé case avec une constante qui égale la valeur de expression, le controle est transféré à
l'instruction qui suit cet énoncé ;
· si un tel case n'existe pas, et si l'énoncé default existe, alors le controle est transféré à l'instruction qui suit
l'énoncé default ;
· si la valeur de expression ne correspond à aucun case et s'il n'y a pas d'énoncé default, alors aucune
instruction n'est exécutée.
Attention. Lorsqu'il y a branchement réussi à un énoncé case, toutes les instructions qui
le suivent sont exécutées, jusqu'à la fin du bloc ou jusqu'à une instruction de rupture
(break). Autrement dit, l'instruction switch s'apparente beaucoup plus à une sorte de goto
« paramétré » (par la valeur de l'expression) qu'à l'instruction case...of... de Pascal.
Exemple (idiot) :
j = 0;
switch (i) {
case 3:
j++;
case 2:
j++;
case 1:
j++;
}
Si on suppose que i ne peut prendre que les valeurs 0 ... 3, alors l'instruction ci-dessus a le même effet que l'affectation
j = i.
Pour obtenir un comportement similaire à celui du case...of... de Pascal, on doit utiliser l'instruction break, comme
dans l'exemple suivant, dans lequel on compte la fréquence de chaque chiffre et des caractères blancs dans un texte :
nb_blancs = nb_autres = 0;
for (i = 0; i < 10; )
nb_chiffre[i++] = 0;
while ((c = getchar()) != EOF)
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
nb_chiffre[c - '0']++;
break;
case ' ':
case 'n':
case 't':
nb_blancs++;
break;
default:
nb_autres++;
}
III-B-8 - Instructions break et continue
Le langage C par Henri Garreta
- 56 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Formats :
break;
continue;
Dans la portée d'une structure de controle (instructions for, while, do et switch), l'instruction break provoque l'abandon
de la structure et le passage à l'instruction écrite immédiatement derrière. L'utilisation de break ailleurs qu'à l'intérieur
d'une de ces quatre instructions constitue une erreur (que le compilateur signale).
Par exemple la construction
for (expr1 ; expr2 ; expr3) {
...
break;
...
}
équivaut à
{
for (expr1 ; expr2 ; expr3) {
...
goto sortie;
...
}
sortie: ;
}
L'instruction continue est moins souvent utilisée. Dans la portée d'une structure de controle de type boucle (while,
do ou for), elle produit l'abandon de l'itération courante et, le cas échéant, le démarrage de l'itération suivante. Elle
agit comme si les instructions qui se trouvent entre l'instruction continue et la fin du corps de la boucle avaient été
supprimées pour l'itération en cours : l'exécution continue par le test de la boucle (précédé, dans le cas de for, par
l'exécution de l'expression d'incrémentation).
Lorsque plusieurs boucles sont imbriquées, c'est la plus profonde qui est concernée par les instructions break et
continue. Dans ce cas, l'emploi de ces instructions ne nous semble pas ¾uvrer pour la clarté des programmes.
III-B-9 - Instruction return
Formats :
return expression ;
et
return ;
Dans un cas comme dans l'autre l'instruction return provoque l'abandon de la fonction en cours et le retour à la
fonction appelante.
Le langage C par Henri Garreta
- 57 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Dans la première forme expression est évaluée ; son résultat est la valeur que la fonction renvoie à la fonction
appelante ; c'est donc la valeur que l'appel de la fonction représente dans l'expression ou il figure. Si nécessaire,
la valeur de expression est convertie dans le type de la fonction (déclaré dans l'en-tête), les conversions autorisées
étant les mêmes que celles faites à l'occasion d'une affectation.
Dans la deuxième forme, la valeur retournée par la fonction reste indéterminée. On suppose dans ce cas que la
fonction appelante n'utilise pas cette valeur ; il est donc prudent de déclarer void cette fonction. Absence d'instruction
return dans une fonction. Lorsque la dernière instruction (dans l'ordre de l'exécution) d'une fonction est terminée, le
controle est rendu également à la procédure appelante. Tout se passe comme si l'accolade fermante qui termine le
corps de la fonction était en réalité écrite sous la forme
...
return;
}
Le langage C par Henri Garreta
- 58 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
IV - Fonctions
Beaucoup de langages distinguent deux sortes de sous-programmes 24 : les fonctions et les procédures. L'appel
d'une fonction est une expression, tandis que l'appel d'une procédure est une instruction. Ou, si on préfère, l'appel
d'une fonction renvoie un résultat, alors que l'appel d'une procédure ne renvoie rien.
En C on retrouve ces deux manières d'appeler les sous-programmes, mais du point de la syntaxe le langage ne
connait que les fonctions. Autrement dit, un sous-programme est toujours supposé renvoyer une valeur, même
lorsque celle-ci n'a pas de sens ou n'a pas été spécifiée. C'est pourquoi on ne parlera ici que de fonctions. C'est dans
la syntaxe et la sémantique de la définition et de l'appel des fonctions que résident les principales différences entre
le C original et le C ANSI. Nous expliquons principalement le C ANSI, rassemblant dans une section spécifique (cf.
section 4.2) la manière de faire du C original.
*
24La notion de sous-programme (comme les procedures de Pascal, les subroutines de Fortran, etc.) est supposée
ici connue du lecteur.
IV-A - Syntaxe ANSI ou avec prototype"
IV-A-1 - Définition
Une fonction se définit par la construction :
typeopt ident ( déclaration-un-ident , ... déclaration-un-ident )
instruction-bloc
Notez qu'il n'y a pas de point-virgule derrière le )" de la première ligne. La présence d'un
point-virgule à cet endroit provoquerait des erreurs bien bizarres, car la définition serait
prise pour une déclaration (cf. section 4.1.4).
La syntaxe indiquée ici est incomplète ; elle ne convient qu'aux fonctions dont le type est défini simplement, par un
identificateur. On verra ultérieurement (cf. section 5.4.1) la manière de déclarer des fonctions rendant un résultat
d'un type plus complexe.
La première ligne de la définition d'une fonction s'appelle l'en-tête, ou parfois le prototype, de la fonction. Chaque
formule déclaration-un-ident possède la même syntaxe qu'une déclaration de variable 25.
Exemple.
int extract(char *dest, char *srce, int combien) {
/* copie dans dest les combien premiers caractères de srce */
/* renvoie le nombre de caractères effectivement copiés */
int compteur;
for (compteur = 0; compteur < combien && *srce != '�'; compteur++)
*dest++ = *srce++;
*dest = '�';
return compteur;
}
Le langage C par Henri Garreta
- 59 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Contrairement à d'autres langages, en C on ne peut pas définir une fonction à l'intérieur d'une autre : toutes les
fonctions sont au même niveau, c'est-à-dire globales. C'est le cas notamment de main, qui est une fonction comme
les autres ayant pour seule particularité un nom convenu.
*
25Restriction : on n'a pas le droit de « mettre en facteur » un type commun à plusieurs arguments. Ainsi, l'en-tête de
la fonction extract donnée en exemple ne peut pas s'écrire sous la forme char *extract(char *dest, *srce, int n).
IV-A-2 - Type de la fonction et des arguments
L'en-tête de la fonction définit le type des objets qu'elle renvoie. Ce peut être :
· tout type numérique ;
· tout type pointeur ;
· tout type struct ou union.
Si le type de la fonction n'est pas indiqué, le compilateur suppose qu'il s'agit d'une fonction à résultat entier. Ainsi,
l'exemple précédent aurait aussi pu être écrit de manière équivalente (mais cette pratique n'est pas conseillée) :
extract(char *dest, char *srce, int combien)
etc.
Fonctions sans résultat. Lorsqu'une fonction ne renvoie pas une valeur, c'est-à-dire lorsqu'elle corres- pond plus à
une procédure qu'à une vraie fonction, il est prudent de la déclarer comme rendant un objet de type voidvoid. Ce type
est garanti incompatible avec tous les autres types : une tentative d'utilisation du résultat de la fonction provoquera
donc une erreur à la compilation. Le programmeur se trouve ainsi à l'abri d'une utilisation intempestive du résultat
de la fonction. Exemple :
void extract(char *dest, char *srce, int combien) {
/* copie dans dest les combien premiers caractères de srce */
/* maintenant cette fonction ne renvoie rien */
int compteur;
for (compteur = 0; compteur < combien && *srce != '�'; compteur++)
*dest++ = *srce++;
*dest = '�';
}
Fonctions sans arguments. Lorsqu'une fonction n'a pas d'arguments, sa définition prend la forme
typeopt ident ( void )
instruction-bloc
On notera que, sauf cas exceptionnel, on ne doit pas écrire une paire de parenthèses vide dans la déclaration ou la
définition d'une fonction. En effet, un en-tête de la forme
type ident()
ne signifie pas que la fonction n'a pas d'arguments, mais (cf. section 4.2.2) que le programmeur ne souhaite pas que
ses appels soient controlés par le compilateur.
Le langage C par Henri Garreta
- 60 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
IV-A-3 - Appel des fonctions
L'appel d'une fonction se fait en écrivant son nom, suivi d'une paire de parenthèses contenant éventuellement une
liste d'arguments effectifs.
Notez bien que les parenthèses doivent toujours apparaitre, même si la liste d'arguments
est vide : si un nom de fonction apparait dans une expression sans les parenthèses, alors il
a la valeur d'une constante adresse (l'adresse de la fonction) et aucun appel de la fonction
n'est effectué.
Passage des arguments. En C, le passage des arguments se fait par valeur. Cela signifie que les arguments formels
26 de la fonction représentent d'authentiques variables locales initialisées, lors de l'appel de la fonction, par les
valeurs des arguments effectifs 27 correspondants.
Supposons qu'une fonction ait été déclarée ainsi
type fonction ( type1 arg formel1 , ... typek arg formelk )
etc.
alors, lors d'un appel tel que
fonction ( arg effectif 1 , ... arg effectif k )
la transmission des valeurs des arguments se fait comme si on exécutait les affectations :
arg formel1 = arg effectif 1
...
arg formelk = arg effectif k
Cela a des conséquences très importantes :
· les erreurs dans le nombre des arguments effectifs sont détectées et signalées,
· si le type d'un argument effectif n'est pas compatible avec celui de l'argument formel correspondant, une
erreur est signalée,
· les valeurs des arguments effectifs subissent les conversions nécessaires avant d'être rangées dans
les argu- ments formels correspondants (exactement les mêmes conversions qui sont faites lors des
affectations).
Remarque. Le langage ne spécifie pas l'ordre chronologique des évaluations des
arguments effectifs d'un appel de fonction. Ces derniers doivent donc être sans effets
de bord les uns sur les autres. Par exemple, il est impossible de prévoir quelles sont les
valeurs effectivement passées à une fonction lors de l'appel :
x = une_fonction(t[i++], t[i++]); /* ERREUR !!! */
Si i0 est la valeur de i juste avant l'exécution de l'instruction ci-dessus, alors cet appel peut aussi bien se traduire
par une fonction(t[i0], t[i0 + 1]) que par une fonction(t[i0 + 1], t[i0]) ou même par une fonction(t[i0], t[i0]). Ce n'est
s^urement pas indifférent !
Le langage C par Henri Garreta
- 61 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
Appel d'une fonction inconnue. En C une fonction peut être appelée alors qu'elle n'a pas été définie (sous-entendu
: entre le début du fichier et l'endroit ou l'appel figure).
Le compilateur suppose alors
· que la fonction renvoie un int,
· que le nombre et les types des arguments formels de la fonction sont ceux qui correspondent aux arguments
effectifs de l'appel (ce qui, en particulier, empêche les controles et conversions mentionnés plus haut).
De plus, ces hypothèses sur la fonction constituent pour le compilateur une première déclaration de la fonction. Toute
définition ou déclaration ultérieure tant soit peu différente sera qualifiée de « redéclaration illégale » 28.
Lorsque les hypothèses ci-dessus ne sont pas justes, en particulier lorsque la fonction ne renvoie pas un int, il faut :
· soit écrire la définition de la fonction appelée avant celle de la fonction appelante,
· soit écrire, avant l'appel de la fonction, une déclaration externe de cette dernière, comme expliqué à la
section 4.1.4.
*
26 Les arguments formels d'une fonction sont les identificateurs qui apparaissent dans la définition de la fonction,
déclarés à l'intérieur de la paire de parenthèses.
*
27 Les arguments effectifs d'un appel de fonction sont les expressions qui apparaissent dans l'expression d'appel,
écrites à l'intérieur de la paire de parenthèses caractéristiques de l'appel.
*
28C'est une erreur surprenante, car le programmeur, oubliant que l'appel de la fonction a entrainé une déclaration
implicite, con»coit sa définition ou déclaration ultérieure comme étant la première déclaration de la fonction.
IV-A-4 - Déclaration externe" d'une fonction
Une déclaration externe d'une fonction est une déclaration qui n'est pas en même temps une définition. On « annonce
» l'existence de la fonction (définie plus loin, ou dans un autre fichier) tout en précisant le type de son résultat et le
nombre et les types de ses arguments, mais on ne donne pas son corps, c'est-à-dire les instructions qui la composent.
Cela se fait en écrivant
· soit un en-tête identique à celui qui figure dans la définition de la fonction (avec les noms des arguments
formels), suivi d'un point-virgule ;
· soit la formule obtenue à partir de l'en-tête précédent, en y supprimant les noms des arguments formels.
Par exemple, si une fonction a été définie ainsi
void truc(char dest[80], char *srce, unsigned long n, float x) {
corps de la fonction
}
alors des déclarations externes correctes sont :
Le langage C par Henri Garreta
- 62 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
http://c.developpez.com/cours/poly-c/
extern void truc(char dest[80], char *srce, unsigned long n, float x);
ou
ou extern void machin(char [80], char *, unsigned long, float);
Ces expressions sont appelées des prototypes de la fonction. Le mot extern est facultatif, mais le point- virgule est
essentiel. Dans la première forme, les noms des arguments formels sont des identificateurs sans aucune portée.
IV-B - Syntaxe originale ou sans prototype"
IV-B-1 - Déclaration et définition
Définition. En syntaxe originale la définition d'une fonction prend la forme
typeopt ident ( ident , ... ident )
déclaration ... déclaration
instruction-bloc
Les parenthèses de l'en-tête ne contiennent ici que la liste des noms des arguments formels. Les déclarations de
ces arguments se trouvent immédiatement après l'en-tête, avant l'accolade qui commence le corps de la fonction.
Exemple :
int extract(dest, srce, combien)
/* copie dans dest les combien premiers caractères de srce */
/* renvoie le nombre de caractères effectivement copiés */
char *dest, char *srce;
int combien;
{
int compteur;
for (compteur = 0; compteur < combien && *srce != '�'; compteur++)
*dest++ = *srce++;
*dest = '�';
return compteur;
}
Déclaration externe. En syntaxe originale, la déclaration externe de la fonction précédente prend une des formes
suivantes :
extern int extract();
ou
int extract();
Comme on le voit, ni les types, ni même les noms, des arguments formels n'apparaissent dans une déclaration
externe 29.
Le langage C par Henri Garreta
- 63 -
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par
quelque moyen que ce soit est soumise