Par
Patrick Gonord
IV. Introduction : Eléments d'un programme C
IV-A. Grandeurs en C
IV-A-1. Valeurs
IV-A-2. Objets
IV-A-2-a. Type d'un objet
IV-A-2-b. Adresse d'un objet
IV-A-2-c. Valeur d'un objet
IV-A-2-d. Création et destruction des objets
IV-A-2-e. Opérations sur les objets
IV-A-3. Types en C
IV-B. Expressions
IV-B-1. Opérateurs et opérandes
IV-B-2. Règle de base sur les opérandes
IV-B-3. Evaluation
IV-B-4. Evaluation des expressions
IV-C. Instructions
IV-C-1. Expressions
IV-C-2. Instructions de contrôle
IV-C-3. Bloc d'instructions
IV-D. Fonctions
IV. Introduction : Eléments d'un programme C
Ce chapitre présente les éléments principaux entrant dans la
composition d'un programme C. Il introduit des principes généraux,
utiles pour la compréhension du langage, qui seront détaillés
dans la suite du cours.
IV-A. Grandeurs en C
Les grandeurs que manipule le programme C peuvent être
classées en deux catégories : des valeurs ou des objets
(ou variables). Les valeurs représentent une information
alors que les objets sont des éléments permettant le
stockage des informations.
IV-A-1. Valeurs
Une valeur est une grandeur abstraite, non modifiable
par nature, exprimant une information. Par exemple le
nombre entier 5 , la valeur booléenne Vrai, la couleur
Rouge,...
Quelques remarques pour préciser la notion de valeur :
a- Il n'est évidemment pas possible de modifier ce qu'est l'entier 5
pour qu'il devienne la même entité que, par exemple, l'entier 3.
On peut éventuellement changer la couleur de "quelque chose", mais
on ne peut changer ce qu'est la couleur Rouge. Ce "quelque chose"
possède alors (entre autres) une propriété de couleur qui (peut être)
peut changer. On utilisera pour le représenter un objet pour stocker
cette information (et celles décrivant ses autres propriétés).
En conclusion, l'assignation n'est pas définie pour les valeurs.
b- Je ne peux pas faire n'importe quoi avec cet entier 5. Par
exemple, écrire 5+Vrai n'a pas de sens. Par contre,
personne ne fera d'objections à ce que j'écrive 5+3 .
c- Ce qu'est l'entier 5 n'est pas modifié que je le représente en
chiffres arabes 5 ou en chiffres romains V ou
en base binaire 101 plutôt qu'en base décimale. Mais il
faut convenir d'une représentation, d'un codage, pour se
comprendre.
Si je choisis de coder les nombres en chiffres romains, comment faire
la différence entre l'entier codé V et la lettre de l'alphabet V ?
Un même codage peut représenter deux choses différentes.
Le codage ne permet pas en général de savoir le genre de valeur
à laquelle on a affaire. Si on ne connaît pas le genre de la valeur
codée, on ne peut pas interpréter le code et la valeur est inconnue.
On pourrait envisager de résoudre cette ambiguïté en tenant compte
du contexte d'emploi de la valeur ou en ajoutant dans le code une
information indiquant son genre. La solution adoptée en C est
différente.
Pour résoudre ces problèmes, chaque valeur en C possède un type
qui définit :
1- L'ensemble auquel appartient la valeur (dans l'exemple
de l'entier 5, l'ensemble (ou plutôt un sous-ensemble) des entiers)
ce qui permet de savoir quelles opérations sont définies
pour les valeurs de ce type et ce que ces opérations font exactement ;
|
2- La représentation (le codage) en machine utilisée pour
les valeurs de ce type.
|
Une valeur peut être parfois composée à partir d'autres
valeurs : Par exemple, une valeur représentative d'une
date peut être considérée comme composée de trois
autres valeurs : le jour, le mois et l'année (si cette
composition convient, car elle n'est pas obligatoire).
On dira dans ce cas que la valeur est un agrégat.
(Note : Le seul cas en C de valeurs agrégats sont les
valeurs de type struct)
IV-A-2. Objets
Un objet, ou variable, est un conteneur d'information. Il a
un caractère très concret en ce sens qu'un objet est situé
quelque part dans le monde physique. On utilise un objet pour
représenter une donnée du programme dont la valeur peut changer
: le prix du pétrole, l'âge du capitaine,...
En C, un objet est caractérisé
par son type, son adresse et sa valeur.
IV-A-2-a. Type d'un objet
Les propriétés d'un objet sont caractérisées par un type
qui définit :
1- Le type de l'information qui est stockée par l'objet
et si l'objet est un agrégat (composé d'autres objets)
ou non ;
|
2- Les opérations qui sont définies pour cet objet ;
|
3- La représentation en mémoire de l'objet et notamment la
taille mémoire qu'il occupe. Pour créer un objet, il faut
connaître la place mémoire dont il a besoin, donc préciser
son type au moment de sa création ;
|
4- Le mode d'accès à l'information stockée par l'objet.
Cet accès est fait à partir de ce qu'on appellera ici
"la valeur de l'objet" (pour des raisons qui apparaîtront
lorsqu'on parlera des règles applicables aux opérandes).
|
Le type d'un objet n'est pas modifiable.
(Note : Les objet agrégats en C sont les tableaux et les objets
de type struct)
IV-A-2-b. Adresse d'un objet
Une valeur permet de caractériser l'emplacement d'un objet :
son adresse.
Pour accéder à l'information qu'il contient,
soit pour la lire soit pour la modifier, il faut savoir
d'une manière ou d'une autre où l'objet se trouve,
donc connaître son adresse. Comment est représentée
cette adresse est sans importance : il suffit qu'à
partir de cette valeur on puisse accéder à l'objet.
On a donc besoin d'avoir un type particulier de valeurs pour
représenter les adresses d'objets. Ces valeurs seront
décrites au chapitre ???
Les objets qui stockent ce type d'information sont appelés
des pointeurs.
L'adresse d'un objet n'est pas modifiable, autrement dit on ne
peut pas déplacer un objet.
IV-A-2-c. Valeur d'un objet
La valeur d'un objet permet d'accéder à l'information stockée
par l'objet. Deux cas se présentent :
-
L'objet est un agrégat du genre tableau ;
dans ce cas, la valeur de l'objet est l'adresse où se
trouve stockée l'information.
Connaissant cette adresse, on peut alors
accéder à l'information. Cette adresse étant non modifiable,
la valeur d'un objet du genre tableau est non modifiable.
Le type de l'objet est alors "tableau de ...",
différent de celui de sa valeur qui est "adresse de ...".
-
Pour tous les autres objets,
la valeur de l'objet est directement l'information
contenue dans l'objet.
Si on lit la valeur de l'objet, on lit
l'information qu'il contient ; si on modifie la valeur de
l'objet, on modifie l'information qu'il contient.
La valeur de l'objet a alors le même type que celui de
l'information qu'il contient et ce type sera également le
type de l'objet.
Note : les tableaux seront décrits en détails
au chapitre ???
La valeur d'un objet est en général modifiable. Les deux exceptions
sont
- les objets du genre tableau
- les objets dont la valeur a été déclarée explicitement constante
par le mot réservé const.
IV-A-2-d. Création et destruction des objets
Deux actions fondamentales sont associées à un objet :
sa création et sa destruction.
- Créer un objet consiste à lui réserver une place suffisante
en mémoire (à une adresse qu'on ne peut pas modifier).
Si l'objet a été déclaré constant par le mot clé
const, sa valeur n'est pas
modifiable après sa création et une valeur doit donc lui
être attribuée au moment même de sa création (initialisation).
- Détruire l'objet consiste à reprendre la place mémoire qui
lui a été attribuée et la rendre à nouveau disponible ;
l'adresse de l'objet devient invalide et essayer d'accéder à
l'objet conduit alors à une catastrophe.
Il est donc important de connaître les procédures conduisant
à la création ou à la destruction des objets.
IV-A-2-e. Opérations sur les objets
Trois opérations sont définies pour tous les objets. Elles
permettent :
-
d'obtenir l'objet situé à une adresse donnée :
Si A est l'adresse d'un objet, l'objet situé à
cette adresse est *A
A est une valeur et *A est un objet ;
-
d'obtenir l'adresse d'un objet :
Si Objet est un objet, son adresse est &Objet
C'est une valeur.
Il s'ensuit que *&Objet est l'objet Objet ;
-
d'obtenir la place mémoire occupée par un objet :
Si Objet est un objet,
sizeof Objet est la taille
en bytes occupée par Objet en mémoire.
Note : un byte est la plus petite quantité
mémoire repérable par une adresse ; sa valeur dépend des
systèmes (souvent, mais pas toujours, un octet soit 8 bits).
IV-A-3. Types en C
Le diagramme ci-dessous récapitule les différents types de données
disponibles en C. Ils seront décrits en détails dans les chapitres
suivants.
IV-B. Expressions
Une expression décrit un ensemble d'opérations portant sur des
valeurs ou des objets. Elle est constituée d'opérateurs et d'opérandes.
Dans sa forme la plus réduite, ce peut être simplement une valeur
(ce qui n'a d'utilité que si cette expression constitue un appel à
une fonction).
IV-B-1. Opérateurs et opérandes
Un opérateur indique une opération à effectuer ; ses opérandes sont
les grandeurs utilisées pour réaliser l'opération.
En C, selon l'opérateur, il peut avoir un, deux ou trois opérandes ;
on parle alors d'un opérateur unaire, binaire (rien à voir ici
avec le code binaire) ou ternaire.
Selon l'opérateur, les opérandes doivent être des valeurs ou
des objets et le résultat de l'opération peut être une valeur ou un objet.
Ainsi, on a déja vu les opérateurs unaires *, &
et sizeof :
-
L'opérateur unaire * a un opérande qui est une valeur et
le résultat est un objet (celui situé à l'adresse spécifiée par l'opérande).
-
L'opérateur unaire & a un opérande qui est un objet
et un résultat qui est une valeur (l'adresse de l'objet désigné par l'opérande).
-
Le cas de l'opérateur sizeof
est particulier :
Son opérande peut être un objet, un type ou une valeur.
Le résultat est une valeur entière donnant la taille mémoire
de l'objet, d'un objet de ce type ou d'un objet du même type
que la valeur.
On peut citer comme exemples simples d'opérateurs binaires l'addition +
et la multiplication * de deux entiers comme dans les expressions
2+3 et 2*3. L'évaluation de ces expressions donnera des valeurs,
respectivement les entiers 5 et 6. Ces deux opérateurs ont deux opérandes
qui sont des valeurs et donne un résultat qui est une valeur.
Certains opérateurs donnent comme résultat une valeur à caractère booléen,
c'est à dire portant comme information Vrai ou Faux.
C'est le cas des opérateurs de comparaison.
Par exemple, l'opérateur == compare ses opérandes pour savoir
si ils sont égaux : 2==3 a une valeur qui signifiera Faux
et 3==3 une valeur qui signifiera Vrai.
Les valeurs à caractère booléens seront décrites en détails au
chapitre ???.
IV-B-2. Règle de base sur les opérandes
Un opérateur possède donc un contexte qui définit la nature, valeur
ou objet, de chaque opérande et du résultat de l'opération. Les règles
suivantes établissent comment le contexte de l'opérateur est respecté :
1- L'opérateur attend un objet comme opérande. Alors l'opérande
doit être un objet.
|
2- L'opérateur attend une valeur comme opérande. Alors si l'opérande
désigne un objet, ce sera interprété comme la valeur de l'objet.
|
Exemple : Considérons l'opérateur binaire = qui permet de
modifier la valeur d'un objet et l'expression Obj = V.
- Contexte : Obj doit être un objet (non déclaré constant); V est une valeur.
L'opérateur = donne comme résultat une valeur, égale à celle
donnée à l'objet et du même type que celui-ci.
- On peut écrire (supposons que Obj est d'un type entier)
Obj = 2 pour que Obj prenne pour valeur 2. Cette
expression a pour valeur 2.
On peut aussi écrire Obj1 = Obj2 où Obj2 est un objet
(que nous supposerons aussi de type entier). Comme Obj2 est
placé dans un contexte de valeur, il désigne ici la valeur
de Obj2 qui deviendra la valeur de Obj1.
- A noter que comme la valeur d'un objet tableau n'est pas modifiable,
on ne pourra jamais écrire Obj = ... si Obj est un objet
tableau. Autrement dit, l'opérateur = n'est pas défini pour
les objets tableaux.
IV-B-3. Evaluation
Pour évaluer le résultat, il faut évaluer les opérandes puis faire
l'opération. Pour les opérateurs binaires, se pose la question de
l'ordre d'évaluation des opérandes. La réponse est (en général)
que cet ordre est indéterminé et son choix est laissé à la
convenance du compilateur (les trois exceptions concernent les
opérateurs binaires && et || ainsi que
l'opérateur , (virgule) qui seront décrits ultérieurement).
Le résultat ne doit pas dépendre de l'ordre d'évaluation des
opérandes sinon il est imprévisible et l'expression est mal formée.
Que se passe t-il si les deux opérandes sont de types différents,
par exemple un entier et un réel comme dans 2+3.5 ?
Dans ce cas, le compilateur va essayer de trouver un type commun
pour lequel l'opérateur est défini et transformer les deux
opérandes dans ce type commun pour effectuer l'opération (cette
transformation d'une valeur est appelé transtypage ou cast).
Naturellement, il doit exister une règle de transformation des
opérandes de leur type en le type commun. Le résultat sera alors
de ce type commun. Le choix du type commun obéit à des règles
précises qui seront énoncées au moment opportun.
IV-B-4. Evaluation des expressions
Un opérateur peut avoir comme opérande une expression pourvu que le
résultat de celle-ci soit une valeur ou un objet selon les exigences
du contexte de l'opérateur. Si l'expression complète comporte plusieurs
opérateurs, comment est-elle interprétée ?
Considérons l'expression 2*3+4 .
La question se pose donc de savoir
comment le compilateur va interpréter cette expression : comme
le produit de 2 et 3 auquel on ajoute 4 (et le résultat sera 10)
ou comme le produit de 2 et de la somme de 3 et 4 (et dans ce cas
le résultat sera 14) ?
Une possibilité est de mettre des parenthèses pour lever cette
ambiguïté et écrire, selon le résultat désiré, (2*3)+4 ou
2*(3+4).
En l'absence de parenthèses l'ambiguïté est levée en considérant
deux propriétés des opérateurs : leur priorité et
leur associativité. Ainsi 2*3+4 est interprété
comme (2*3)+4 et le résultat sera 10.
Le mécanisme priorité+associativité sera décrit en détail plus loin.
Comme autre exemple, considérons l'expression légale A = B = C
où, pour simplifier, A, B et C ont le même type.
Sans même connaître le mécanisme priorité+associativité, on peut
être sûr qu'il faut l'interpréter comme A = (B = C).
L'interprétation (A = B) = C est impossible : (A = B)
donne pour résultat une valeur or on doit avoir
objet = ... ce qui viole le contexte des opérandes
de l'opérateur =.
Considérons donc A = (B = C). B =... impose que B
soit un objet. De même, A = ... impose que A soit un
objet. C est une valeur (ou la valeur d'un objet).
B = C est une valeur égale (ici) à celle de C. Au final,
A et B prennent pour valeur celle de C ;
L'expression a pour valeur la nouvelle valeur de A et a le même
type que A.
IV-C. Instructions
On peut distinguer deux groupes d'instructions : les instructions formées
d'une expression et les instructions de contrôle.
IV-C-1. Expressions
En dehors des instructions de contrôle, que nous verrons ensuite,
toutes les instructions d'un programme C sont des expressions
(terminées par un ;) : exécuter ces instructions consiste simplement
à évaluer ces expressions.
Pour qu'une instruction ait une influence sur le programme et serve
à quelque chose, il faut que cette évaluation modifie l'état du programme ;
on dit alors qu'elle a des effets de bords. Le plus souvent,
l'effet de bord consiste à modifier la valeur d'un objet, à lire
des données écrites dans un fichier ou à partir du clavier ou à
écrire des données dans un fichier ou sur la console.
Exemple :
L'instruction 2+3; n'a absolument aucune action, ne fait rien et
est inutile.
L'instruction A = 2+3; (où A est un objet) a un effet
de bord : elle modifie la valeur de l'objet A.
Toutes les expressions ne sont pas des instructions : elles peuvent,
par exemple, servir à établir les conditions de fonctionnement
d'une instruction de contrôle ou à initialiser la valeur d'un objet.
IV-C-2. Instructions de contrôle
L'exécution d'un programme C est séquentielle : Après avoir exécuté une
instruction, on exécute l'instruction suivante dans le programme.
Ce processus peut être modifié en utilisant des instructions de contrôle
de flux du programme. Elles permettent d'exécuter une partie du
programme uniquement si une condition est remplie, de répéter
l'exécution d'une partie du programme tant qu'une condition est remplie,
etc.
Ces instructions de contrôle seront décrites en détails au chapitre
???.
IV-C-3. Bloc d'instructions
La plupart des instructions de contrôle de flux du programme
conditionne l'exécution d'une seule instruction. Si, et le cas est
très fréquent, vous souhaitez soumettre à condition l'exécution de
plus d'une instruction, vous pouvez grouper ces instructions dans
un bloc, c'est à dire englober les instructions entre les signes
{ et }.
Là où la syntaxe demande une instruction, vous pouvez placer
un bloc d'instructions.
IV-D. Fonctions
Une fonction sert à effectuer une opération ou une action.
En ce sens, on peut la considérer comme un opérateur défini par le
programmeur : ses arguments jouent le rôle des opérandes d'un opérateur
et à partir de la valeur de ses arguments, elle peut fournir un résultat.
Comme un opérateur, une fonction peut avoir des effets de bords
Une fonction doit effectuer un calcul (ou une action)
précis et bien délimité. Les fonctions qui font plusieurs choses à la fois
ont peu de souplesse d'emploi et leur utilisation est restreinte à une
situation spécifique. Ecrivez plutôt plusieurs fonctions,
chacune dédiée à une tâche précise et que vous pourrez ainsi réutiliser.
Exemple : Vous souhaitez calculer la valeur de N! où N est un entier
entré au clavier et afficher le résultat sur la console.
Ecrivez une fonction qui prend en argument
la valeur N , calcule sa factorielle et renvoie la valeur obtenue.
La fonction peut jouer alors un rôle équivalent à celui d'un opérateur
de calcul de la factorielle, opérateur qui n'existe pas en C.
Mais la fonction ne doit pas lire sur le clavier la valeur de N ni
afficher la valeur obtenue sur l'écran. Sinon la fonction
est inutilisable si N est une valeur calculée par le programme ou lue
dans un fichier ou si vous n'avez pas besoin d'affichage de la valeur obtenue.
La procédure à suivre est donc :
- Lire au clavier la valeur de N
- Utiliser la fonction avec cette valeur comme argument
- Afficher le résultat à la console
Trois étapes que vous pouvez grouper dans une autre fonction, le calcul de
la factorielle restant disponible pour être utilisé ailleurs.
Les fonctions ne doivent pas être conçues simplement pour découper
arbitrairement votre code en tranches plus petites mais pour le
structurer, le rendre plus clair et pour éviter de dupliquer des morceaux
de code identiques qui nuisent à la maintenance du programme. C'est
pourquoi, vous serez amené à écrire de nombreuses fonctions de très
petite taille. Il est d'ailleurs rare qu'on soit contraint d'écrire des
fonctions dépassant une centaine de lignes et dans un
tel cas, on doit se poser la question : Est-ce que ma fonction ne fait
pas trop de choses ?
Il est également rare que les fonctions nécessitent un grand nombre d'arguments.
Si c'est votre cas, posez-vous la question : Est-ce que mes données sont
bien organisées, bien représentées par les types de données que j'ai choisis ?
Une fonction doit former un ensemble autonome, c'est à dire que son
fonctionnement ne doit dépendre que des arguments passés à la fonction
et non pas d'éléments qui lui sont extérieurs. De cette façon, une fois que
sont définis son rôle et les paramètres dont elle a besoin, vous pouvez
écrire son code en faisant totalement abstraction du reste du programme.
La fonction écrite et testée, l'utilisateur peut totalement ignorer le
code de la fonction. Seul son "mode d'emploi" lui est nécessaire : savoir
précisement ce qu'elle fait et ce que représente chaque paramètre. Elle
est alors devenu un outil qui peut être utilisé et réutilisé à loisir.
En C, les arguments des fonctions sont toujours des valeurs et
le résultat est lui aussi toujours une valeur (jamais un objet).

