C Cours
C Cours
0 – 16/02/2010
LE LANGAGE C
TODO :
v1.1.11.0 – 16/02/2010
peignotc(at)arqendra(dot)net / peignotc(at)gmail(dot)com
Toute reproduction partielle ou intégrale autorisée selon les termes de la licence Creative Commons (CC) BY-NC-SA : Contrat
Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique 2.0 France, disponible en ligne
[Link] ou par courrier postal à Creative Commons, 171 Second Street, Suite 300,
San Francisco, California 94105, USA. Merci de citer et prévenir l’auteur.
1 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
2 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Initialisation............................................................................................................................................. 23
2.5.3 Principes de mises en œuvre des tableaux à plusieurs dimensions ................................................24
[Link] Déclaration .............................................................................................................................................. 24
[Link] Affectation et utilisation .......................................................................................................................... 24
[Link] Initialisation............................................................................................................................................. 24
2.6 LES CHAÎNES DE CARACTÈRES ................................................................................................. 25
2.6.1 Définition........................................................................................................................................25
2.6.2 Principes de mise en œuvre ............................................................................................................25
[Link] Déclaration .............................................................................................................................................. 25
[Link] Affectation et utilisation .......................................................................................................................... 25
[Link] Initialisation............................................................................................................................................. 26
3 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Affectation............................................................................................................................................... 42
[Link] Utilisation ................................................................................................................................................ 42
[Link] Initialisation............................................................................................................................................. 43
5.2 LES ÉNUMÉRATIONS ................................................................................................................ 43
5.2.1 Définition........................................................................................................................................43
5.2.2 Principes de mise en œuvre ............................................................................................................43
[Link] Définition ................................................................................................................................................ 43
[Link] Déclaration .............................................................................................................................................. 43
[Link] Affectation............................................................................................................................................... 43
[Link] Utilisation ................................................................................................................................................ 44
[Link] Initialisation............................................................................................................................................. 44
5.3 LES UNIONS ............................................................................................................................. 44
5.3.1 Définition........................................................................................................................................44
5.3.2 Principes de mise en œuvre ............................................................................................................44
[Link] Définition ................................................................................................................................................ 44
[Link] Déclaration .............................................................................................................................................. 44
[Link] Affectation............................................................................................................................................... 44
[Link] Utilisation ................................................................................................................................................ 45
[Link] Initialisation............................................................................................................................................. 45
5.4 LES CHAMPS DE BITS ............................................................................................................... 45
5.4.1 Définition........................................................................................................................................45
5.4.2 Principes de mise en œuvre ............................................................................................................45
[Link] Définition ................................................................................................................................................ 45
[Link] Utilisation ................................................................................................................................................ 45
5.5 LES TYPES SYNONYMES ........................................................................................................... 46
6 FLUX D’ENTRÉES/SORTIES SUR FICHIERS................................................. 47
6.1 INTRODUCTION ........................................................................................................................ 47
6.2 GESTION DES FLUX .................................................................................................................. 47
6.2.1 Déclaration.....................................................................................................................................47
6.2.2 Ouverture........................................................................................................................................47
6.2.3 Fermeture .......................................................................................................................................48
6.3 LECTURE ET ÉCRITURE ............................................................................................................ 48
6.3.1 Lecture dans un fichier ...................................................................................................................48
6.3.2 Écriture dans un fichier..................................................................................................................48
6.3.3 Autres fonctions de gestion.............................................................................................................49
6.3.4 Gestion des erreurs.........................................................................................................................49
4 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
5 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
6 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
1 INTRODUCTION AU LANGAGE C
Le langage C est un langage de programmation qui a été inventé au début des années 70 par les créateurs du système
d’exploitation Unix afin de faciliter le développement de celui-ci. À l’époque, l’assembleur était le langage le plus
utilisé pour ce genre de programmation, mais par manque de fiabilité et de simplicité il fut abandonné au profit d’un
nouveau langage, construit sur les bases des langages Algol et B (laboratoires Bell) 1.
Le C est un langage compilé, présentant les caractéristiques d’un langage de haut niveau (évolué et structuré), tout
en offrant les mêmes possibilités que l’assembleur (proche du langage machine).
De par sa conception et sa très large diffusion, le C est un langage très ouvert, qui dispose d’un certain nombre de
bibliothèques (graphiques, mathématiques, réseaux, etc.), et est capable de s’interfacer avec de nombreux autres
langages, aussi bien des langages de plus bas niveau (assembleur) que de plus haut niveau (C++, Java, etc.).
Malgré sa conception assez ancienne, c’est un langage encore très utilisé dans les entreprises, à la fois par habitude,
et à la fois par ses nombreux avantages ; cela reste notamment un choix de langage pertinent lorsque l’on veut
travailler avec une partie opérative matérielle.
1
A… B… C !!… ou « comment trouver un nom ».
7 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
2 NOTIONS DE BASE
Le langage C est un langage compilé : à partir du fichier contenant le code source écrit en langage C, un fichier
exécutable final est généré ; celui-ci pourra être exécuté par toute machine utilisant même processeur et même système
d’exploitation que ceux de la machine utilisée lors du développement 3.
La création d’un fichier exécutable à partir d’un fichier source C suit les étapes suivantes 4 :
#include <stdio.h> 110001001010101101 101100101100100011
int main(void)
{
000111010100010… 110110110100110… ordinateur
float a;
...
compilation édition fichier exécution
fichier fichier binaire système
source C binaire des liens exécutable d’exploitation
(.c) (.obj) (.exe)
bibliothèques
La compilation consiste à traduire le fichier source (.c) écrit en langage C (compréhensible par l’être humain) en
code binaire (.obj) non exécutable (compréhensible par l’ordinateur, appelé code machine).
Cette compilation peut être décomposée elle-même en 3 phases :
S exécution des directives préprocesseur ;
S gestion des références de variables et de fonctions ;
S conversion du programme (sans les fonctions de bibliothèques) en code machine.
L’édition des liens (appelée aussi link) consiste à charger le code machine des fonctions de bibliothèques (.obj) et à
l’associer avec le code machine du fichier source (.obj) afin de créer un fichier binaire exécutable (.exe).
L’exécution correspond au traitement, par le processeur, du fichier binaire exécutable qui est chargé au fur et à
mesure en mémoire centrale (mémoire vive).
1
Environnement de Développement Intégré.
2
Code::Blocks et Dev-C++ (son prédécesseur) sont des EDI gratuits disponibles en téléchargement respectivement à l’adresse
[Link] et [Link]
3
Certains compilateurs proposent la possibilité de compiler pour une cible spécifique autre que celle utilisée pour la compilation (autre système
d’exploitation par exemple).
4
Appelée « chaîne de compilation ».
8 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
/* fonctions */
Pour spécifier que plusieurs lignes de code appartiennent au même ensemble (fonction, groupe, etc.), on les encadre
par les caractères ‘{’ et ‘}’ (accolades) ; un ensemble de lignes de code regroupées en utilisant les accolades constitue
ce qu’on appelle un bloc de code, ou bloc d’instructions.
Toute ligne d’instruction située à l’intérieur d’un bloc de code 1 se termine par le caractère ‘;’ (point-virgule) ; le
retour à la ligne n’est pas interprété par le compilateur comme la fin de la ligne d’instruction 2.
Ex. : Un programme permettant de calculer le prix TTC à partir d’un prix HT saisi par l’utilisateur.
#include <stdio.h> // inclusion de la bibliothèque stdio.h (STanDard Input Ouput)
#define TVA 19.6 // définition d'une constante
int main(void)
{
float HT, TTC; // déclaration de deux variables
1
Que ce bloc de code corresponde à une fonction, à la fonction principale, ou aux instructions d’une boucle, d’un test, etc.
2
Il faut donc être très attentif. Fort heureusement, le compilateur saura détecter les erreurs résultant de cet oubli ; par expérience, lorsqu’un
programme, une fois compilé, contient beaucoup d’erreurs, c’est très souvent à cause d’un point-virgule oublié, car toutes les lignes suivant ce
point-virgule deviennent alors incompréhensibles pour le compilateur.
3
De manière générale, il convient de commenter avec pertinence en expliquant l’utilité d’une ou plusieurs lignes de code dans la finalité du
programme, plutôt que de surcharger le code source avec des commentaires paraphrastiques. Exemple : pour if (nbpersonnes < 10), on
dira « on vérifie que le nombre de personnes ne dépasse pas le quota », plutôt que « on teste si nbpersonnes est inférieur à 10 ».
9 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Toute fonction effectue son traitement en utilisant des informations qui lui sont propres et éventuellement aussi en
utilisant des informations externes, appelées paramètres d’entrée ; ceux-ci permettent d’exécuter l’opération en
utilisant des données variables et/ou fournies au moment de l’exécution seulement.
Ex. : Soit une fonction effectuant l’affichage de données à l’écran ; cette fonction a comme paramètres d’entrée les
données que l’on désire afficher.
printf("ceci est un texte"); // fonction avec 1 paramètre
printf("ceci est le résultat : %d", var1); // fonction avec 2 paramètres
Chaque fonction effectue un traitement qui lui est propre ; si le traitement produit un résultat de sortie sous forme
alphanumérique, donc exploitable au niveau du code, celui-ci peut être récupéré et ré-utilisé dans le code source.
Pour récupérer le résultat d’une fonction, il faut, au niveau du code, assimiler celle-ci à son résultat ; c’est-à-dire
qu’il faut considérer qu’à l’exécution, la fonction sera remplacée par son résultat. Celui-ci peut ainsi être ré-utilisé
directement pour l’affectation d’une variable, comme paramètre d’une autre fonction, pour un calcul, etc.
Ex. : Soit une fonction permettant de générer un nombre aléatoire ; le résultat produit sera le nombre généré.
rand(); // génération d'un nombre entier aléatoire
nba = rand() + 1; // utilisation d'un nombre aléatoire dans un calcul
printf("nombre aléatoire : %d", rand()); // affichage d'un nombre aléatoire
En résumé, une fonction est un module « opaque » qui effectue un traitement, en utilisant des paramètres d’entrée
qu’on lui fournit, et qui produit un résultat de sortie symptomatique du traitement réalisé.
1
Hérité de manière rétro-ascendante du langage C++ car, historiquement, le standard ANSI C n’autorise pas ce type de commentaires.
2
Comme ceci est ignoré par le compilateur, le point-virgule final doit être placé en fin de ligne d’instruction, avant le début du commentaire.
3
Commandes de bas-niveau parmi 5 types : arithmétique, branchement, entrées/sorties, logique ou transfert.
4
Exemple en assembleur : ADD AX,[0100] réalise l’opération suivante : prendre le contenu stocké à l’adresse 0x100, l’ajouter au contenu du
registre AX, et stocker le résultat dans le registre AX.
5
Certaines fonctions admettent plusieurs syntaxes différentes, le choix adéquat étant laissé au développeur en fonction de ses besoins.
6
Une bibliothèque permet de regrouper par thématique plusieurs fonctions (cf. 3.3).
10 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Nb : Il est important, dans la syntaxe d’une fonction, de ne pas confondre l’entrée et la sortie de la fonction 1 :
résultat = nom_fonction(paramètres); // sortie = nom_fonction(entrée)
Nb : Une directive préprocesseur n’est pas une instruction, elle ne fait pas partie du code final généré. Donc la règle
du point-virgule final ne s’applique pas.
1
Cette syntaxe est similaire à celle d’une fonction en mathématiques : y = f(x), la sortie y est fonction de l’entrée x.
2
Par habitude, le nom d’une constante est écrit en majuscule.
3
Main (eng) principal (fr).
4
cf. 3.2.2.
5
À l’image de la notion d’inconnue en mathématiques (ex. : soit x le nombre de billes que Jean a gagné […]).
6
Spécifique au programme, au programmeur, à l’environnement de développement, aux us et coutumes de l’entreprise, etc.
11 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
… mémoire
0x1999
adresses 0x2000 variable
0x2001
…
La taille de l’espace mémoire réservé est fonction du type de données que la variable représente. L’adresse d’une
variable correspond à sa position dans l’espace mémoire total.
L’espace mémoire est segmenté en zones mémoire d’une taille fixe et qui est voulue minimale. Si une variable, pour
être stockée, nécessite plus de place que ne le permet 1 seule zone mémoire, alors 1 ou plusieurs zones mémoire
contiguës supplémentaires sont utilisées.
… mémoire
0x1999
adresses 0x2000 variable (début)
0x2001 variable (fin)
…
Figure 2.3 : espace mémoire réservé pour une variable nécessitant plusieurs zones mémoire
La notion de variable s’oppose à la notion de littéral, qui correspond à toute information exprimée directement :
4, -5.49, "texte", 'c', etc. Pour conserver la valeur d’un littéral, il suffit d’utiliser une variable.
La déclaration d’une variable garantit que le compilateur lui réserve un espace mémoire spécifique, et ce pour toute
la durée d’exécution du bloc de code dans lequel elle est déclarée.
Le nom d’une variable doit respecter les critères suivants :
S nom unique et distinct des mots-clefs du langage C qui sont réservés (cf. annexe A) ;
S nom uniquement composé de caractères alphanumériques ou du caractère souligné (underscore), pas de
chiffre en début de nom ;
S longueur maximale de 32 caractères (standard C ANSI) ;
1
S casse distinguée (minuscules et majuscules différenciées) .
[Link] Affectation
L’affectation d’une variable consiste à lui donner une valeur, en utilisant l’opérateur ‘=’ (égal), suivant la syntaxe
nom_variable = valeur;.
Nb : La valeur à affecter doit être du même type que celle de la variable, sinon le compilateur détectera une erreur 2.
[Link] Utilisation
L’utilisation d’une variable consiste à se servir de la valeur qu’elle contient. Pour cela, il suffit de mentionner son
mnémonique à l’endroit du code où l’on désire utiliser sa valeur ; lors de l’exécution du programme, celui-ci sera alors
remplacé par la valeur de la variable qu’il représente au moment même où il est interprété par le processeur.
1
À proscrire cependant, afin d’éviter les confusions (ex. : I et i).
2
Par exemple, il est impossible de stocker une valeur réelle dans une variable déclarée entière.
12 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Initialisation
L’initialisation d’une variable consiste à effectuer une première affectation à cette variable ; en effet, toute variable
doit être initialisée avant d’être utilisée 1. On peut déclarer et initialiser une variable en même temps :
Ex. : int nbeleves = 24; // déclaration et initialisation de la variable nbeleves
int demigroupe = nbeleves / 2; // déclaration et initialisation de demigroupe
Le type char correspond en fait à un type entier codé sur 1 octet. 256 caractères différents peuvent donc être codés ;
l’ensemble des caractères utilisables en langage C est défini par le standard ASCII 4 étendu.
Ex. : char d;
d = 'B'; // d est affecté avec le caractère B (identique à d = 66;)
d = 67; // d est affecté avec la valeur 67 (identique à d = 'C';)
Les 32 premiers caractères de la table ASCII sont des caractères de contrôle, donc non-affichables, mais que l’on
peut préciser par une symbolique précise, commençant par le caractère spécial ‘\’ (barre oblique inversée (antislash)).
La plupart sont maintenant désuets mais d’autres restent encore très utiles.
caractère description valeur (en hexa)
\n saut à la ligne 0x0a
\r retour chariot 0x0d
\t tabulation horizontale 0x09
\b retour arrière 0x08
\f saut de page 0x0c
\a signal sonore 0x07
Ex. : char c = '\n'; // déclaration d'une variable caractère 'saut à la ligne'
1
Le compilateur peut être paramétré pour initialiser automatiquement les variables qui ont été oubliées, mais il est déconseillé de travailler ainsi.
2
cf. annexe B.
3
Afin d’éviter la confusion avec une éventuelle variable.
4
ASCII : American Standard Code for Information Interchange (cf. annexe E).
13 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Le caractère ‘\’ est un caractère spécial en langage C ; il précise que le caractère qui le suit doit être interprété
différemment d’un simple caractère alphanumérique. Un certain nombre de caractères ont ainsi une double fonction,
selon qu’ils soient précédés de \ ou non.
Ex. : char c = 'r'; // déclaration d'une variable caractère r minuscule
char d = '\r'; // déclaration d'une variable caractère 'retour chariot'
1
char e = '\\'; // déclaration d'une variable caractère \ (antislash)
Pour faire référence directement à un code ASCII, on peut utiliser la syntaxe \xHH avec HH représentant la valeur
hexadécimale du code ASCII du caractère.
Ex. : j = i * 4;
j = j + 2;
Ex. : i = 4;
j = 4;
k = i++ + 2; // identique à (k = i + 2; i = i + 1;), k vaut 6
k = ++j + 2; // identique à (j = j + 1; k = j + 2;), k vaut 7
1
Réponse à la question : « Comment afficher le caractère ‘\’ (antislash) puisque celui-ci est un caractère spécial ? ». Idem pour ‘'’ (apostrophe)
qui sert pour encadrer un caractère : char f = '\''; et ‘"’ (guillemets) qui sert pour encadrer une chaîne de caractères : char g = '\"';.
2
Liste complète en C.2.
14 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Pour indiquer le format d’une variable à afficher, on utilise le symbole ‘%’ (pourcent) suivi du code spécifique au
format de la variable :
S d : décimal ;
S x : hexadécimal ;
S u : entier non signé ;
S f : réel ;
S lf : double ;
S e : réel en notation exposant ;
S c : caractère.
De plus, on peut dimensionner l’affichage de la valeur, en incluant certains codes entre le symbole ‘%’ et la lettre du
format de la variable :
S X (X nombre entier) : nombre total de symboles à afficher ;
S 0X (X nombre entier) : nombre total de symboles à afficher en complétant à gauche avec des 0 non
significatifs par rapport au nombre de chiffres maximal supporté par le format ;
S .Y (Y nombre entier) : nombre total de décimales à afficher ;
S M.E (M et E nombres entiers) : cas particulier du format d’affichage en notation exposant %e f %[Link]) ;
3
S + : signe (‘+’ ou ‘–’) ;
S <espace> : signe (caractère espace pour valeur positive ou ‘–’ pour valeur négative) ;
S – : cadrage à gauche.
Nb : Pour afficher un caractère, on peut aussi utiliser la fonction putchar() / putc() suivant la syntaxe
putchar(c); / putc(c); où c est une variable de type caractère.
1
cf. C.3.
2
À mentionner dans les directives préprocesseur donc.
3
Par défaut, si une valeur positive est affichée, le signe ‘+’ n’est pas précisé, alors que pour une valeur négative, le signe sera précisé.
15 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Le premier paramètre correspond au format de la variable devant être saisie 2 ; ce format doit être spécifié en suivant
exactement la même syntaxe que pour la fonction printf().
Le second paramètre correspond au nom de la variable dans laquelle doit être stockée la saisie au clavier ; ce nom de
variable doit absolument être précédé du symbole ‘&’ (ET commercial) 3.
Ex. : int i;
printf("valeur = ");
scanf("%d", &i); // i : entier
La fonction scanf() est bloquante, c’est-à-dire que lors de l’exécution du programme, celle-ci sera stoppée jusqu’à
ce que l’utilisateur ait validé la saisie par la touche <entrée>.
Pour indiquer le format d’une variable à saisir, on utilise les mêmes symboliques de format que pour l’affichage (%d,
%x, %f, %c, etc.).
Nb : Pour saisir un caractère, on peut aussi utiliser la fonction getchar() (validation par <entrée>) / getch() (pas
de validation) suivant la syntaxe c = getchar(); / c = getch(); où c est une variable de type caractère.
1
Cette syntaxe est simplifiée ; la syntaxe réelle est identique à printf() : scanf(format, &variable1, &variable2, ...);. C’est-à-
dire que l’on peut saisir plusieurs variables avec un seul appel à scanf().
2
En réalité, ce paramètre correspond à texte+format. Cependant, si l’on indique du texte en plus du format, la saisie devra alors inclure ce texte
pour que le stockage de la variable s’opère correctement ; ex. : soit la saisie scanf("i= %d", &i); il faut que l’utilisateur tape exactement
"i= 4" pour que la valeur 4 soit stockée dans la variable i ; si l’utilisateur saisit "4", cela stockera alors une valeur indéfinie dans la variable
(la fonction cherche à lire exactement "i= " à partir du tampon d’entrée, puis lit une valeur entière (%d) qu’elle stocke dans la variable i).
3
La fonction scanf() permet de stocker une valeur saisie directement dans un espace mémoire, et non via une variable ; la notation &var
renvoie à l’espace mémoire réservé à la variable var en utilisant pour l’opérateur ‘&’ – aucun lien avec l’opérateur binaire – (cf. chapitre 4).
4
Attention : l’évaluation de « x vaut-il y ? » s’écrit donc (x == y) et pas (x = y), qui est une affectation de la valeur de y dans x, et qui par
définition, vaut vraie lorsque tout se passe bien (la valeur de y a bien été positionnée dans x) ; en résumé, l’« évaluation » (x = y) est
toujours vraie !
16 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Le résultat obtenu est de type booléen (0 si expression fausse, valeur autre que 0 si expression vraie).
Ex. : (i == 4) // pour vérifier que la variable i est égale à 4
(i != j) // pour vérifier que la variable i est différente de la variable j
Lorsque l’expression doit prendre en compte plusieurs conditions, on utilise les opérateurs logiques, ainsi que les
parenthèses pour assurer la priorité d’évaluation 1.
Ex. : ((i == 4) && (j > 6)) // vérifie que i vaut 4 et que j est supérieur à 6
Nb : Lorsqu’une expression de type entier est nulle, elle est considérée fausse ; si elle n’est pas nulle, elle est
considérée vraie.
Ex. : (i) // vérifie que i est différent de 0, identique à (i != 0)
(!i) // vérifie que i vaut 0, identique à (i == 0)
(i && j) // vérifie que i et j sont différents de 0
VRAI FAUX
expression
instructions à instructions à
exécuter si exécuter si
expression vraie expression fausse
Pour réaliser un test simple, on utilise l’instruction if () éventuellement associée avec l’instruction else, suivant
la syntaxe :
if (expression)
{
/* instructions à exécuter si expression vraie */
}
else
{
/* instructions à exécuter si expression fausse */
}
Chaque bloc d’instructions (expression vraie et expression fausse) doit être encadré par les caractères ‘{’ et ‘}’
(accolades).
1
Il existe une priorité naturelle entre les différents opérateurs (cf. C.3).
17 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Ex. : Tester la valeur de la variable x ; si x vaut 1, on affiche gagné, sinon on affiche perdu.
if (x == 1)
{
printf("gagne\n");
}
else
{
printf("perdu\n");
}
Lorsque le bloc d’instructions ne comprend qu’une seule instruction, les accolades peuvent être omises.
Ex. : if (x == 1) printf("gagne\n");
else printf("perdu\n");
Dans le cas où aucune instruction ne doit être exécutée lorsque l’expression évaluée est fausse, l’instruction else
n’est pas mentionnée.
VRAI FAUX
expression
instructions à
exécuter si
expression vraie
Ex. : Tester la valeur de la variable x ; si x vaut 2, on affiche gagné, sinon on ne fait rien.
if (x == 2)
{
printf("gagne\n");
}
Pour réaliser un test multiple, on utilise les instructions switch () et case éventuellement associées avec
l’instruction default, suivant la syntaxe :
switch (variable)
{
case valeur1 : /* instructions si variable vaut valeur1 */
break;
case valeur2 : /* instructions si variable vaut valeur2 */
break;
...
default : /* instructions si variable ne vaut aucune des valeurs */
}
18 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
switch (choix)
{
case '1' : printf("liste par groupe");
break;
case '2' : printf("liste par ordre alphabétique");
break;
default : printf("choix non prévu");
}
VRAI FAUX
expression
instructions à
répéter si
expression vraie
19 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Pour réaliser une répétition avec aucune exécution au minimum, on utilise l’instruction while (), suivant la
syntaxe :
while (expression)
{
/* instructions à répéter si expression vraie */
}
Nb : Si l’expression évaluée est fausse avant le début de l’exécution de l’instruction while (), le bloc
d’instructions ne sera jamais exécuté, pas même une seule fois.
Attention : Lors de l’exécution d’une boucle, il faut s’assurer que les variables mises en œuvre dans l’expression
évaluée et assurant ainsi la continuité ou l’arrêt de la boucle, soient modifiées dans le bloc d’instructions, sans quoi on
risque de réaliser ce qu’on appelle une boucle infinie 1. À moins que l’expression de continuité utilise des valeurs
issues de capteurs 2, il faut donc prendre soin de vérifier qu’au moins l’une des variables de l’expression soit modifiée
dans les instructions du bloc.
Ex. : Boucle infinie.
int i = 0;
while (i < 10) // i n'est jamais modifié, donc i est toujours inférieur à 10
{
printf("gagne\n");
}
Ex. : Boucle infinie minimale (parfois utile en mode « débogage à la main » 3).
while (1);
instructions à
répéter si
expression vraie
FAUX
expression
VRAI
1
Ce qui bloque l’exécution de votre programme, et monopolise le processeur, puisque le même bloc d’instructions est répété à l’infini.
2
Donc externes au programme lui-même, et susceptibles d’être modifiées même si le programme est « bloqué ».
3
Permet de positionner un point d’arrêt manuel pour pouvoir visualiser le comportement du programme (affichage de valeurs de variables, etc.)
20 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Pour réaliser une répétition avec au moins 1 exécution, on utilise les instructions do et while (), suivant la
syntaxe :
do
{
/* instructions à répéter si expression vraie */
}
while (expression);
Nb : Si l’expression évaluée est fausse avant le début de l’exécution des instructions do while (), le bloc
d’instructions sera quand même exécuté une fois 1.
Là encore, les risques de boucle infinie sont identiques à ceux de l’instruction while ().
Ex. : do { } while (1); // boucle infinie minimale
initialisation
VRAI FAUX
expression
instructions à
répéter si
expression vraie
modification
Pour réaliser une répétition contrôlée, on utilise l’instruction for (), suivant la syntaxe :
for (initialisation ; expression ; modification)
{
/* instructions à répéter si expression vraie */
}
1
À l’inverse de la boucle « tant que… faire ».
21 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Nb : Les tableaux sont une manière de représenter en programmation les matrices, utilisées en calculs algébriques.
1
Parfois aussi appelée dimension, à ne pas confondre avec l’appellation tableau à 1 ou plusieurs dimensions.
22 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Déclaration
La déclaration d’un tableau se réalise comme une déclaration de variable, précisée de la taille du tableau en
utilisant les symboles ‘[’ et ‘]’ (crochets), suivant la syntaxe type_données nom_tableau[taille_tableau];.
Ex. : Soit un ensemble de 10 variables de type réel.
float notes[10]; // déclaration d'un tableau de 10 réels
La déclaration d’un tableau garantit que le compilateur lui réserve un espace mémoire spécifique 1, et ce pour toute
la durée d’exécution du bloc de code dans lequel il est déclaré.
Nb : La taille du tableau est nécessairement une valeur numérique, et ne peut être définie par une autre variable 2.
Attention : En langage C, le premier indice est l’indice numéro 0 ; pour un tableau d’une taille N, l’indice maximal
est donc N-1. Ceci est très important, d’autant plus qu’il n’y a aucune vérification lors de la compilation du
programme que l’indice utilisé n’est pas hors des limites du tableau 3.
indice n°0 indice n°1 indice n°2 indice n°(N-1)
Si on ne peut pas utiliser une variable pour déclarer la taille d’un tableau, on peut en revanche utiliser une variable
comme indice du tableau.
Ex. : float notes[5]; // déclaration d’un tableau
int i;
Nb : Il est important de savoir si la variable que l’on manipule est du type tableau ou variable simple ; en effet, dans
le cas d’un tableau, les éléments sont accessibles avec la syntaxe tableau[indice] et la notation tableau
correspond en fait à l’adresse en mémoire du début du tableau, usuellement appelée pointeur 4.
[Link] Initialisation
L’initialisation d’un tableau consiste à effectuer une première affectation de chacun de ses éléments, en utilisant la
syntaxe suivante : type_données nom_tableau[taille_tableau] = {val_0, ..., val_N-1};.
Ex. : float notes[5] = {4.0, 12.5, 15.0, 9.0, 16.5}; // initialisation du tableau
1
L’espace mémoire réservé correspond à la somme de l’espace mémoire nécessaire à chacune des variables ; dans l’exemple float
notes[10], mettant en jeu 10 variables réelles (4 octets), l’espace mémoire total réservé est donc de 10 x 4 = 40 octets.
2
Néanmoins, en fixant une constante à l’aide de la directive préprocesseur #define, on peut définir la taille du tableau à l’aide de cette
constante.
3
L’erreur ne sera alors possiblement détectée qu’à l’exécution ; le risque de construire un exécutable buggé est donc très important.
4
cf. chapitre 4.
23 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Nb : De part l’abstraction nécessaire à la gestion des tableaux à plusieurs dimensions, on utilise de manière générale
rarement des tableaux de dimension supérieure à 2, parfois 3.
Ex. : Soit un tableau à 2 dimensions ; on dispose donc de 2 indices pour référencer ses éléments (on parle alors
généralement de ligne et colonne 1) et on peut les représenter suivant une grille.
N colonnes
L0;C0 L0;C1 L 0 ; C (N-1)
M lignes
La déclaration, l’affectation, l’utilisation et l’initialisation d’un tableau à plusieurs dimensions suivent les mêmes
principes que les tableaux à 1 dimension.
[Link] Déclaration
La déclaration d’un tableau à plusieurs dimensions se fait en utilisant la syntaxe suivante :
type_données nom_tableau[taille_dim_1][taille_dim_2]...[taille_dim_max];.
Ex. : Soit un tableau à 2 dimensions de taille 2 et 3, contenant des éléments de type entier.
int valeurs[2][3]; // déclaration d'un tableau d'entiers à 2 dimensions
[Link] Initialisation
L’initialisation consiste à effectuer une première affectation de chacun des éléments du tableau, suivant la syntaxe :
type_données nom_tableau[t_dim_1][t_dim_2]...[t_dim_max] =
{ {val_0_0, ..., val_0_N-1}, {val_1_0, ...}, ...};
1
Même si dans le cas d’un tableau à 2 dimensions on parle de lignes et de colonnes, ce n’est qu’un moyen d’abstraction pour l’être humain. Ce
qui veut dire que le premier indice n’est pas spécifiquement représentatif de la ligne ou de la colonne ; c’est au programmeur d’en décider à la
déclaration du tableau, puis de s’y tenir tout au long de son programme. En effet, dans la mémoire de l’ordinateur, les informations ne sont pas
stockées sous forme de lignes et de colonnes, mais sous forme de liste de valeurs (tableau à 1 dimension de N * M éléments).
24 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Déclaration
La déclaration se fait en suivant la syntaxe char nom_chaine[taille_max];.
Ex. : Soit une chaîne de caractères de 10 caractères au maximum (soit 11 avec le caractère fin de chaîne).
char str[11]; // déclaration d'un tableau de 11 caractères
Nb : Cela n’est pas important que la chaîne soit plus « petite » que la taille du tableau qui la contient 1, car la fin de
la chaîne est précisément connue grâce au caractère fin de chaîne, qui est le caractère NUL (noté ‘\0’ ou bien 0x00).
On peut afficher directement l’intégralité de la chaîne de caractères jusqu’au caractère fin de chaîne, en utilisant la
fonction printf() associée au format d’affichage %s.
Ex. : printf("%s", str); // affichage de la chaine complète (jusqu'au caractère 0x00)
Mais on peut aussi utiliser la fonction puts(), spécifique aux chaînes de caractères, suivant la syntaxe
puts(nom_chaine).
En revanche, il est impossible d’affecter directement une valeur à une chaîne, hormis en passant par la saisie ou
l’initialisation.
Pour saisir une chaîne, on peut utiliser la fonction scanf(), associée au code de format %s.
Attention : Comme une chaîne est un tableau, le nom de la chaîne correspond à l’adresse 2 du début du tableau, il ne
faut donc pas utiliser l’opérateur ‘&’.
Ex. : scanf("%s", str); // saisie d'une chaine
1
Cas où la chaîne de caractères n’est pas connue au lancement du programme, ou si elle est susceptible d’être modifiée en cours d’exécution ; il
suffit juste de prévoir le dimensionnement en fonction de la taille maximale.
2
cf. chapitre 4.
25 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Cependant, l’utilisation de la fonction scanf() n’est pas recommandée car elle ne permet pas de saisir de chaîne
comportant un espace, celui-ci étant considéré comme un délimiteur de fin de saisie.
Il est préférable d’utiliser la fonction gets(), suivant la syntaxe gets(nom_chaine).
Ex. : gets(str); // saisie d'une chaine pouvant comporter des espaces
[Link] Initialisation
L’initialisation d’une chaîne de caractères consiste à lui affecter une valeur de départ, c’est-à-dire effectuer une
première affectation de chacun des caractères, en utilisant l’une ou l’autre des deux syntaxes suivantes :
S char nom_chaine[taille_chaine] = "chaine"; ;
S char nom_chaine[taille_chaine] = {car_0, ..., car_max, '\0'};.
Nb : Même si une chaîne de caractères est représentée, en langage C, par un tableau de caractères, il ne faut pas
confondre ; un tableau de caractères restera toujours un tableau de caractères, et il ne peut être considéré ou utilisé
comme chaîne que s’il possède un caractère fin de chaîne.
26 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
3 LES FONCTIONS
3.1 GÉNÉRALITÉS
Une fonction correspond à un traitement réalisé par le programme ; un traitement étant composé de une ou plusieurs
instructions. Une fonction est la forme généralisée d’un sous-programme ou procédure.
Toute fonction est définie dans une bibliothèque 1 qu’il faut mentionner dans l’en-tête du programme afin que la
phase d’édition des liens puisse trouver le code pré-compilé correspondant.
Chaque fonction possède sa propre syntaxe, il convient donc de la respecter scrupuleusement ; certaines fonctions
admettent d’ailleurs plusieurs syntaxes différentes, utilisables à volonté selon les besoins.
Toute fonction effectue son traitement en utilisant des informations internes (propres à la fonction) et
éventuellement aussi en utilisant des informations externes, appelées paramètres d’entrée ou arguments ; ceux-ci
permettent d’exécuter le traitement en utilisant des données variables et/ou disponibles seulement au moment de
l’exécution.
paramètres fonction
En langage C, les paramètres d’entrée sont indiqués à la suite du nom de la fonction, entre parenthèses, séparés les
uns des autres par une virgule :
nom_fonction(paramètre1, paramètre2, ...); // exécution de la fonction
Ex. : Soit une fonction effectuant l’affichage de données à l’écran ; cette fonction a comme paramètres d’entrée les
données que l’on désire afficher.
printf("ceci est un texte"); // fonction avec un paramètre
printf("ceci est le résultat : %d", var1); // fonction avec deux paramètres
Chaque fonction effectue un traitement qui lui est propre ; si le traitement produit un résultat de sortie sous forme
alphanumérique, donc exploitable au niveau du code, celui-ci peut être récupéré et ré-utilisé dans le code source.
fonction résultat
En langage C, pour récupérer le résultat d’une fonction, il faut, dans le code, assimiler celle-ci à son résultat ; c’est-
à-dire qu’il faut considérer qu’à l’exécution, la fonction sera substituée par son résultat 2. Le résultat peut ainsi être ré-
utilisé directement, pour l’affectation d’une variable, comme paramètre d’une autre fonction, etc.
var = nom_fonction(); // exécution de la fonction et affectation du résultat
// dans une variable
1
Une bibliothèque permet de regrouper par thématique plusieurs instructions (entrées/sorties, mathématiques, graphiques, …).
2
Tout comme une variable est remplacée, à l’exécution, par sa valeur.
27 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Ex. : Soit une fonction permettant de générer un nombre aléatoire ; le résultat produit sera le nombre généré.
rand(); // génération d'un nombre entier aléatoire
nba = rand() + 1; // utilisation du résultat de la fonction dans un calcul
printf("nombre aléatoire : %d", rand()); // affichage du résultat de la fonction
En résumé, une fonction peut être assimilée à une boîte noire 1 qui effectue une opération, en utilisant des
paramètres d’entrée qu’on lui fournit, et qui produit un résultat de sortie symptomatique de l’opération réalisée.
entrée sortie
Figure 3.3 : exécution d’une fonction produisant un résultat en utilisant des paramètres
Nb : Il est important, dans la syntaxe d’une fonction, de ne pas confondre l’entrée et la sortie de la fonction :
résultat = nom_fonction(paramètres);
Le nom permet d’appeler la fonction et de l’exécuter ; ce qui correspond à l’exécution du traitement qu’elle réalise.
Ce nom suit les mêmes règles que celles liées aux noms de variables, mis à part qu’il sera toujours suivi de
parenthèses : nom_fonction().
Ex. : ma_fonction() // fonction ma_fonction()
Les paramètres d’entrée permettent à la fonction de réaliser un traitement adapté à différents cas de figure 2.
Il faut mentionner le type de chacun d’entre eux, en les énumérant dans l’ordre dans lequel ils devront être passés à
l’appel, suivant la syntaxe nom_fonction(paramètre_1, paramètre_2, ...).
Ex. : ma_fonction(int, char) // le premier paramètre est du type int
// le second paramètre est du type char
Le compilateur vérifiera que pour chaque appel de la fonction qui est faite, le type de chacun des paramètres est bien
respecté. Dans le cas où la fonction n’a pas de paramètres d’entrée, on ne mentionne rien, ou bien void.
Le type de résultat en sortie spécifie le type de résultat que la fonction représente. Ce résultat est assimilable à une
variable, et peut ainsi être ré-utilisé pour être affiché, pour affecter une variable existante, pour réaliser des calculs,
comme paramètre d’entrée d’une autre fonction, etc.
On considère que le type du résultat renvoyé par la fonction correspond au type de la fonction ; et on dira la fonction
est du type …. Par conséquent, une fonction possède toujours un type, que l’on spécifie suivant la syntaxe
type_résultat nom_fonction().
Si la fonction ne renvoie rien, on considère alors que cette fonction est du type void 3.
Ex. : int ma_fonction() // fonction renvoyant un résultat de type entier
void ma_fonction_2() // fonction ne renvoyant rien
1
En informatique, les notions de boîte blanche et boîte noire définissent le type de débogage mis en œuvre : respectivement, en connaissant le
fonctionnement interne du programme / du sous-programme / de la fonction que l’on teste, ou bien, sans connaître ce fonctionnement interne.
2
Prémices du concept de ré-utilisabilité – concept extrêmement important en informatique, et très présent en programmation objet.
3
Void (eng) l vide (fr) ; type spécifique correspondant à « rien » utilisé exclusivement pour les fonctions.
28 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Pour pouvoir utiliser une fonction d’une bibliothèque, l’éditeur de liens doit pouvoir accéder à son code afin de créer
un fichier exécutable complet. Il faut donc mentionner, dans les directives préprocesseur, que la bibliothèque décrivant
la fonction utilisée doit être importée, avec la syntaxe #include <nom_bibliothèque.h>.
Ex. : #include <stdlib.h> // importation de la bibliothèque stdlib
Nb : Le fichier de bibliothèque .h 4 contient uniquement le prototype des fonctions ; le lien avec le fichier .c ou .cpp
se fait automatiquement. Cela permet aussi, dès la phase de compilation, de vérifier la syntaxe des fonctions utilisées
dans le programme.
Pour définir une fonction utilisateur, il faut donner son prototype, avec un nom donné à chaque paramètre d’entrée,
ainsi que son code, suivant la syntaxe :
type nom_fonction(type_param_1 nom_param_1, type_param_2 nom_param_2, ...)
{
/* code de la fonction */
}
Ex. : Soit une fonction permettant de calculer la moyenne de deux nombres réels passés en paramètres et renvoyant
le résultat de ce calcul.
float moy2f(float nb1, float nb2)
{
1
Permettant au compilateur de positionner le pointeur de programme au bon endroit dans la pile d’exécution, ceci afin que le processeur sache
où le programme doit commencer à être exécuté.
2
Les habitués du monde DOS ou UNIX utilisent cela tous les jours (ex. : dir c:, dir a*, etc.).
3
Les bibliothèques standard ont été normalisées par le standard C ANSI (cf. annexe D). Lors du développement d’un programme, le respect de
cette normalisation améliore la portabilité du code source.
4
Appelés fichiers d’en-tête (voir le langage C++).
5
Appelés aussi sous-programmes.
29 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
La spécification des paramètres d’entrée (types et noms) correspond en fait à la déclaration de variables internes à la
fonction, qui prennent pour valeurs les valeurs respectives passées en paramètres à l’appel de la fonction. Il s’agit donc
d’une recopie de valeur 1, et si celle-ci provient d’une variable, le paramètre demeure une variable bien distincte de la
variable d’appel 2.
Ex. : La fonction précédente est appelée dans un programme principal.
int main(void)
{
float x = 10.0;
moy2f(1.0, 9.0); // ici, dans moy2f(), nb1 vaudra 1.0 et nb2 9.0
moy2f(6.0, x); // ici, dans moy2f(), nb1 vaudra 6.0 et nb2 10.0 (recopie de
// la valeur de x)
}
Le code d’une fonction utilisateur suit exactement les mêmes règles que n’importe quel code source écrit en C. La
déclaration de variables internes, l’appel de fonctions standard (ou d’autres fonctions utilisateurs), l’affichage, la
saisie, etc. sont possibles.
Ex. : float moy2f(float nb1, float nb2)
{
float result;
Pour déterminer quelle est la valeur que doit retourner la fonction – ce qui correspondra au résultat de sortie –, il faut
le spécifier dans le code de la fonction en utilisant l’instruction return, suivant la syntaxe return valeur;.
Le paramètre de cette instruction doit bien entendu être une valeur ou une variable du même type que celui de la
fonction.
Ex. : float moy2f(float nb1, float nb2)
{
float result;
Dans le programme appelant – le programme ayant appelé la fonction – le résultat de la fonction, soit donc la valeur
associée au return, peut donc être récupérée directement en assimilant le nom de la fonction à ce résultat.
Ex. : La fonction est utilisée dans un programme principal.
int main(void)
{
float x = 10.0;
float var;
var = moy2f(1.0, 9.0); // var vaut le résultat renvoyé par moyenne avec
// comme paramètres 1.0 et 9.0
printf("resultat: %f", moy2f(6.0, x)); // affichage du résultat renvoyé par
// moyenne avec 6.0 et 10.0
}
1
On parle de « passage par valeur » – au contrario du passage par adresse, en utilisant les pointeurs (cf. chapitre 4).
2
Et ce, même si le paramètre porte le même nom que la variable d’appel ; il s’agit de 2 variables distinctes, situées dans 2 blocs de code
distincts, donc occupant 2 espaces mémoires bien distincts.
30 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
int main(void)
{
float x = 10.0;
Cette règle du « sens de lecture » à la compilation doit aussi être respectée lorsqu’une fonction utilisateur utilise une
autre fonction utilisateur.
Une autre possibilité consiste à mentionner, en début de programme, les prototypes de toutes les fonctions
utilisateur ; le compilateur « connaît » ainsi toutes les fonctions utilisateur avant de compiler quoi que ce soit 1.
Ex. : void ma_fonction();
void ma_fonction_2();
void ma_fonction()
{
...
}
void ma_fonction_2()
{
...
}
int main(void)
{
...
}
Enfin, le code d’une fonction utilisateur peut être écrit dans un autre fichier .c que celui du programme principal ;
pour pouvoir appeler la fonction à partir de ce dernier, il suffit de mentionner le chemin du fichier en tant que directive
préprocesseur d’inclusion, suivant la syntaxe #include "chemin_fichier.c".
Ex. : #include "C:\\Mes_Progs\\ma_lib.c" // double '\\' car caractère spécial
Nb : Il est possible de déclarer une variable locale de même nom qu’une variable globale existante ; c’est alors la
variable locale qui a « priorité ».
1
Cette pratique a aussi l’avantage de présenter tous les modules du programme en début de fichier source.
2
Au sens de lecture haut f bas près.
31 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
m = 7;
i++; // incrémentation de la variable globale i
printf("%d", i); // affichage : 3
printf("%f", j); // affichage : 6.2 (le 'j' local "masque" le 'j' global)
}
int main(void)
{
int m = 6;
Nb : On évite dans la mesure du possible de créer des variables globales ; en effet celles-ci sont extrêmement
gourmandes en mémoire (la place mémoire est réservée du début jusqu’à la fin de l’exécution du programme, aucune
libération de mémoire n’est possible en cours d’exécution).
De manière générale, même si cela pourrait paraître parfois plus pratique, il est déconseillé de déclarer dans un
programme 2 variables portant le même nom :
S 1 paramètre d’une fonction / la variable d’appel associée ;
S 1 variable globale / 1 variable locale « cachant » la variable globale ;
S 2 variables de même nom mais distinctes car déclarées dans 2 fonctions différentes ;
S 1 variable / 1 autre variable avec le même nom mais avec une différence de casse ;
S …
Si cela n’a aucune conséquence sur la compilation et sur la qualité de la programmation, cela est source de grande
confusion pour le programmeur et est néfaste pour la lisibilité du code source.
32 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
4 LES POINTEURS
4.1 INTRODUCTION
Toute information est stockée sous forme de variable ; c’est-à-dire qu’un espace mémoire est réservé pour stocker sa
valeur. À cet espace mémoire correspondent donc un contenu (la valeur) et un contenant (l’adresse) lequel référence
sa position dans l’espace mémoire total.
mémoire
espace mémoire
0x1999
réservé pour une
adresses 0x2000 valeur variable
0x2001
En langage C, lors de l’utilisation d’une variable, cette gestion de la mémoire (les adresses utilisées par les
variables) est transparente pour le programmeur 1. Il est cependant possible de travailler directement avec des espaces
mémoire, c’est-à-dire avec des adresses plutôt qu’avec des variables.
L’adresse utilisée par une variable est fixée par le système d’exploitation 2 et est identique tout au long de
l’exécution du programme. Autant il est toujours possible de connaître l’adresse d’une variable, autant il est
impossible de la modifier en cours d’exécution ; il est par contre possible de la fixer au départ, avant toute utilisation,
mais cela est déconseillé sauf en cas de réel besoin 3.
4.2 GÉNÉRALITÉS
4.2.1 Les opérateurs
On peut prendre connaissance de l’adresse utilisée par n’importe quelle variable déclarée dans le programme, en
utilisant l’opérateur d’adresse & 4 suivant la syntaxe &variable.
On fait ainsi référence au contenant, et non plus au contenu.
Ex. : float va = 4; // déclaration et initialisation d'une variable
printf("contenu de va: %f", va); // affichage du contenu
printf("adresse de va: %d", &va); // affichage de l'adresse
printf("adresse de va: %p", &va); // affichage de l'adresse en hexa "pointeur"
Nb : L’adresse d’une variable, et ce quelque soit son type, est nécessairement une valeur de type entière (donc
affichable avec les formats %d, %x, %p).
Inversement, à partir d’une adresse, on peut accéder à la valeur qui y est stockée, en utilisant l’opérateur
d’indirection * 5 (opérateur de contenu) suivant la syntaxe *adresse.
On accède ainsi au contenu à partir du contenant.
1
Ce n’est pas le cas de l’assembleur où l’on travaille principalement avec des espaces mémoires, et pas des variables.
2
En fonction des autres programmes en cours d’exécution ; l’adresse est donc susceptible d’être différente entre 2 exécutions distinctes.
3
Notamment lors d’accès à du matériel, afin d’effectuer des opérations d’entrées/sorties dans des registres positionnés à des adresses bien
spécifiques ; l’inconvénient est que le code produit perd toute portabilité et doit être récrit pour être adapté à une nouvelle plate-forme.
4
À ne pas confondre avec l’opérateur binaire & – ET logique – (cf. C.2).
5
À ne pas confondre avec l’opérateur binaire * – multiplication – (cf. C.2).
33 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Les opérateurs d’adresse et d’indirection réalisent donc 2 opérations inverses, et sont liés par la relation :
si adresse = &variable, alors *adresse = variable (la réciproque est inexacte). De là, on en déduit que :
adresse = &*adresse, et variable = *&variable.
4.2.2 Définition
On appelle pointeur une variable représentant une adresse mémoire ; on dit que le pointeur pointe sur cette adresse.
mémoire
Figure 4.2 : valeur d’une variable dont l’adresse est pointée par un pointeur
Le principal intérêt des pointeurs est de travailler directement avec les adresses, c’est-à-dire avec la mémoire, avec
la possibilité à loisir de lire ou écrire 1 n’importe quelle adresse. On s’affranchit ainsi des déclarations de variables
figées et on peut alors gérer de manière dynamique le stockage des données utilisées lors de l’exécution d’un
programme, aussi bien pour la réservation de l’espace mémoire nécessaire aux données que pour sa libération.
[Link] Affectation
L’affectation d’une valeur à un pointeur s’opère de la même manière que pour une variable, en faisant référence au
contenu et en lui affectant une valeur, en suivant la syntaxe *pointeur = valeur.
Ex. : float *pf;
int *pi, var=7;
*pf = 5.2; // affectation de la valeur 5.2 à l'adresse pointée par pf
*pi = var; // copie de la valeur de var à l'adresse pointée par pi ([ pi=&var;)
1
Avec les risques que cela suppose (ex. : on écrit dans une zone mémoire réservée à la gestion de la carte graphique f au mieux : erreur
d’exécution et arrêt du programme incriminé, au pire : défaillance générale du système d’exploitation).
2
type* m type.
3
En termes de type, un pointeur d’entier (int*) n’est pas du même type qu’un entier (int), idem pour un pointeur de réel et un réel, etc.
34 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
pf f *pf (5.2)
pi f *pi (7)
Attention : La copie de l’adresse (pointeur = &variable) induit que la valeur pointée sera identique
(*pointeur = variable) ; en revanche, la copie du contenu (*pointeur = variable) n’effectue aucune
modification sur l’adresse.
[Link] Initialisation
L’une des principales caractéristiques des pointeurs est la possibilité de créer des variables dynamiquement lors de
l’exécution. La conséquence directe est que, par défaut, aucun espace mémoire n’est réservé pour un pointeur.
Nb : Si aucun espace mémoire n’est spécifiquement réservé à un pointeur, alors son utilisation est très risquée, car
on risque de travailler avec des zones mémoire utilisées par d’autres variables du programme, voire même des données
du système d’exploitation 1, ou bien de travailler à un espace mémoire pour lequel il n’y a aucune garantie
d’exclusivité, et qui peut donc être modifié à tout moment par une tierce application.
L’initialisation d’un pointeur consiste à lui affecter un espace mémoire réservé. Deux cas sont possibles :
S association du pointeur à un espace mémoire déjà réservé ; on utilise pour cela une variable (espace réservé
automatiquement par le compilateur), ou bien un autre pointeur dont l’espace mémoire est déjà réservé ;
Ex. : float *ph, nb=2.7; // déclaration ph : aucun espace mémoire réservé
int *pm;
ph = &nb; // ph pointe sur l'adresse de nb, espace mémoire déjà
// réservé lors de la déclaration "float nb"
pm = (int*) ph; // pm pointe sur l'adresse ph (espace mémoire déjà réservé)
S réservation explicite d’un nouvel espace mémoire ; on réalise pour cela une allocation de mémoire en
utilisant la fonction malloc() de la bibliothèque malloc.h suivant la syntaxe
pointeur = (type_pointeur*) malloc(taille), où la taille est spécifiée en octets, et la valeur
renvoyée est l’adresse à laquelle débute l’espace mémoire nouvellement réservé.
Ex. : char *pn; // déclaration d'1 pointeur sur char
pn = (char*) malloc(sizeof(char)); // allocation d'espace mémoire
//pour 1 char
Cette syntaxe, permet par ailleurs de réserver plusieurs zones mémoire successives, sans réelle limitation.
Ex. : char *po; déclaration d'1 pointeur sur char
po = (char*) malloc(3*sizeof(char)); // allocation d'espace mémoire
// pour 3 char
po f
pn f
1
Ce qui se traduit dans le meilleur des cas par un programme buggé (lorsqu’on peut le déceler), ou dans le pire des cas par un
dysfonctionnement du système d’exploitation.
2
Et afin de soustraire aussi aux différences entre plates-formes ou compilateurs en ce qui concerne la taille allouée à chaque type de variable.
35 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Tout espace mémoire réservé explicitement doit être libéré explicitement 2. La libération de l’espace mémoire
préalablement réservé à un pointeur se fait en utilisant la fonction free() de la bibliothèque malloc.h selon la syntaxe
free(nom_pointeur).
Attention : Il est important d’utiliser les parenthèses à bon escient – voire à chaque fois – dans l’écriture
arithmétique des pointeurs. En effet, l’opérateur d’indirection (*) est prioritaire par rapport aux opérateurs
arithmétiques, mais n’est pas prioritaire par rapport aux opérateurs d’incrémentation/décrémentation.
Ainsi *pointeur+2 signifie (*pointeur)+2 (ajout de 2 au contenu du pointeur) alors que *pointeur++ signifie
*(pointeur++) (accès au contenu de pointeur puis incrémentation de pointeur).
1
Mot-clef. Le pointeur NULL correspond en réalité à l’adresse 0x00, adresse jamais allouée et toujours valide.
2
Convention de programmation : autant de free() que de malloc(), pas 1 de moins, pas 1 de plus.
3
Le type void* est compatible avec tout type pointeur, au contrario du type int* qu’il faut donc transtyper pour initialiser convenablement
l’adresse de début d’un pointeur autre que int*.
4
cf. 2.2.
5
Un caractère étant en fait un entier – codé sur 8 bits en C – (cf. 2.2.4).
6
C’est souvent le cas dans les autres langages aussi, même si cela est bien souvent caché (Java, C#, …).
36 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Lorsque le type d’une variable nécessite plusieurs zones mémoire, l’adresse de la variable correspond à la première
adresse mémoire utilisée (la plus basse). Les octets constituant la variable, quant à eux, sont stockés selon les adresses
croissantes suivant un ordre allant des poids faibles vers les poids forts (type little-endian 1), ou bien selon un ordre
allant des poids forts vers les poids faibles (type big-endian 2), en fonction du type de processeur et/ou de système
d’exploitation utilisé 3.
Nb : Dans la suite de notre étude, nous considérerons que c’est le modèle little-endian qui est utilisé, car il
correspond au type de processeur utilisé le plus couramment dans la vie de tous les jours 4.
Ainsi 2 variables de même type stockées successivement en mémoire, sont séparées de X zones mémoire, où X
représente la taille allouée pour le type de ces variables, et leurs adresses sont donc distantes de X octets.
En conséquence, lorsque l’on accède à un espace mémoire en se basant sur un pointeur déjà initialisé, cet accès est
réalisé en concordance avec le type de variable pointé ; ceci afin d’accéder à un espace mémoire qui correspond à une
variable complète du type pointé, et pas à 2 parties incomplètes de 2 variables différentes mais contiguës en mémoire.
Figure 4.5 : arithmétique des pointeurs et zones mémoire par type de variable
Ex. : Trois pointeurs de différents types dont on modifie l’adresse pointée par incrémentation.
char *pc; // déclaration d'un pointeur sur caractère
int *pi; // déclaration d'un pointeur sur entier
double *pdo; // déclaration d'un pointeur sur nombre réel double
1
Les processeurs type Intel et Alpha, et les systèmes d’exploitation Windows et Linux sont du type little-endian.
2
Les processeurs Sun et Motorola, et le système d’exploitation MacOS sont du type big-endian.
3
Cela a pour conséquence qu’un entier de type int (4 octets = 32 bits), initialisé à 0x12345678 par un processeur/OS 32 bits de type
little-endian, sera lu 0x78563412 par un processeur/OS 32 bits de type big-endian, et vice-versa.
4
On pourrait alors dire que le LSB (Less Significant Byte – Octet le moins significatif) de la variable est stocké à la première adresse ; le MSB
(Most Significant Byte) est stocké à la dernière adresse. S’il y a un bit de signe, celui-ci sera donc le dernier bit du dernier octet.
37 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Figure 4.6 : pointeurs de différents types pointant sur des zones mémoire communes
Ex. : Trois pointeurs de différents types pointant sur des zones mémoire communes.
short *ps; // déclaration d'un pointeur sur entier court
int *pi, i; // déclaration d'un pointeur sur entier
unsigned char *pc; // déclaration d'un pointeur sur caractère (non-signé)
*ps = 0x4321;
*(ps+1) = 0x8765;
Nb : Il faut noter que c’est une erreur (en plus d’être inutile) de vouloir libérer l’espace mémoire des pointeurs pi et
pc ; en effet, aucune allocation spécifique n’a été faite (pas de malloc()) car ils pointent sur le même espace
mémoire que ps. Donc lorsque l’espace mémoire réservé pour ps a été libéré, tous les espaces mémoire réservés
explicitement ont été libérés ; libérer l’espace mémoire de pi ou pc ne sert à rien et peut même générer une erreur
d’exécution.
38 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
4.3.2 Analogies
En réalité, un pointeur et un tableau sont très proches dans leur utilisation. On peut ainsi utiliser un pointeur pour
accéder aux données d’un tableau ; la réciproque n’est pas toujours vraie.
De la même manière qu’il est possible d’accéder à l’adresse d’une variable à l’aide de l’opérateur &, il est possible
de connaître l’adresse d’un tableau en se basant sur l’adresse de la première donnée du tableau, en utilisant la syntaxe
1
&tableau[0], ce qui est identique à l’écriture tableau .
L’adresse de chaque élément d’un tableau est alors obtenue en utilisant la syntaxe &tableau[indice], et on en
déduit que les écritures *(pointeur+indice) et tableau[indice] permettent ainsi d’accéder à la même zone
mémoire 2.
free(pf);
1
Pour un tableau, comme pour un pointeur, son adresse (l’adresse de début) correspond à l’adresse du premier élément.
2
Noter la similitude de syntaxe.
3
Au « mieux », on peut utiliser une constante pour définir la taille d’un tableau, cf [Link].
39 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
1
Tout comme un tableau à 2 dimensions n’est rien d’autre qu’un tableau de tableaux à 1 dimension.
2
type** m type* et type** m type.
40 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
À l’opposé des types de variables simples (entier, réel, caractère) et de leurs déclinaisons, existent des types de
variables complexes, définis par le programmeur.
age : int
initiale : char
Lorsque l’on fait référence à l’un des champs d’une structure, il faut obligatoirement indiquer le nom de la structure
à laquelle appartient le champ. Pour cela, on utilise l’opérateur ‘.’ (point) qui permet de « naviguer » à travers la
structure 1, suivant la syntaxe variable_structuré[Link].
Ex. : Soit eleve une variable structurée de type personne ; pour accéder à chacun des champs age et initiale,
on écrit respectivement [Link] et [Link].
Nb : La définition d’une structure est valide pour le bloc de code dans lequel elle est insérée ; un type structuré
devant être utilisé dans l’ensemble du code source devra donc être défini à la suite des directives préprocesseur 2.
1
Ce même opérateur est utilisé dans les langages objet pour marquer la relation objet.
2
Telle une variable globale.
41 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
[Link] Déclaration
Une fois la structure définie, on peut utiliser directement son nom pour réaliser la déclaration d’une variable du
type structuré, suivant la syntaxe struct nom_structure nom_variable_structure.
Ex. : Une variable du type structuré personne.
struct personne eleve1;
[Link] Affectation
L’affectation d’un champ d’une variable structure s’opère de la même manière que pour une variable simple, en
accédant à chaque champ indépendamment.
Ex. : [Link] = 20; // affectation de 20 au champ age
[Link] = 'm'; // affectation de 'm' au champ initiale
L’affectation d’une variable structure en tant qu’entité à part entière ne peut être réalisée qu’en utilisant une autre
variable structure de même type, déjà initialisée.
Ex. : struct personne eleve2; // déclaration d'une 2nde variable structure
eleve2 = eleve1; // affectation globale
[Link] Utilisation
L’utilisation d’un champ d’une variable structure s’opère de la même manière que pour une variable simple.
Ex. : int diffage;
struct personne eleve3;
[Link] = 18;
diffage = [Link] – [Link]; // utilisation de champs
printf("%c - %d\n", [Link], [Link]); // affichage des champs
L’utilisation d’une variable structure en tant qu’entité se réalise là aussi tout comme une variable simple.
Ex. : void afficherPersonne(struct personne p)
{
printf("age: %d\n", [Link]);
printf("initiale: %c\n", [Link]);
}
int main(void)
{
...
struct personne eleve4;
...
eleve4 = eleve1; // copie d'une variable structurée dans une autre
afficherPersonne(eleve4); // affichage des champs
}
Cependant, seules les opérations d’affectation ou de passage de paramètres ont un sens ; en effet, le nom de la
variable structure est une référence à cette structure, et ne correspond à rien d’exploitable. De fait, une structure peut
être le type de retour d’une fonction.
Ex. : struct personne saisirPersonne()
{
struct personne p;
scanf("%d", &[Link]);
scanf("%c", &[Link]);
return p;
}
int main(void)
{
struct personne eleve5;
eleve5 = saisirPersonne(); // saisie des champs
afficherPersonne(eleve5); // affichage des champs
}
42 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Une variable structure est passée par valeur lors de l’appel d’une fonction ; en conséquence, la modification dans
une fonction d’un champ d’une variable structure passée en paramètre ne modifie pas le même champ de la variable
structure dans le programme appelant.
Ceci peut cependant être réalisé, si on déclare un pointeur de structure plutôt qu’une simple variable structure.
L’écriture *pointeur_structure.champ étant sujette à confusion 1, pour accéder au membre d’une variable
structure pointée on utilise l’opérateur ‘->’ (tiret + supérieur) suivant la syntaxe pointeur_structure->champ.
Ex. : void modifierPersonne(struct personne *p)
{
(p->age)++;
}
int main(void)
{
...
modifierPersonne(&eleve5); // modification d'un champ (on passe l'adresse)
afficherPersonne(eleve5); // affichage des champs
}
[Link] Initialisation
L’initialisation d’une variable structure consiste à initialiser chacun des champs qu’elle contient, dans l’ordre où ils
sont donnés dans la définition de la structure, suivant la syntaxe :
struct nom_structure nom_variable_structure = {val_champ1, val_champ2, ...}
Ex. : struct personne eleve6 = {21, 'n'}; //initialisation d'une variable structure
[Link] Déclaration
Une fois l’énumération définie, on peut utiliser directement son nom pour réaliser la déclaration d’une variable du
type énuméré, suivant la syntaxe enum nom_enumeration nom_variable_enumeration.
Ex. : Une variable du type énuméré semaine.
enum semaine jour;
[Link] Affectation
L’affectation d’une variable énumération s’opère de la même manière que pour une variable simple, en utilisant
l’une des valeurs de la liste de l’énumération ou bien une autre variable énumération de même type, déjà initialisée.
Ex. : enum semaine jourRepos; // déclaration d'une seconde variable énumérée
jour = mer;
jourRepos = jour;
1
Est-ce la variable structure ou le champ qui est du type pointeur ?
43 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Nb : Une énumération demeure une liste d’entiers, et on peut donc manipuler une variable énumérée comme tel.
Ex. : jourRepos = 2; // correspond à 'mer'
jour++;
[Link] Utilisation
L’utilisation d’une constante d’une variable énumération s’opère comme pour une variable simple.
Ex. : int diffjour;
diffjour = dim - jour; // utilisation directe d'une constante énumérée
printf("%d\n", diffjour); // affichage: 4
[Link] Initialisation
L’initialisation d’une variable énumération est par défaut définie automatiquement en commençant par 0 et en
affectant à chaque champ de la liste la valeur entière qui suit la valeur du champ précédent.
On peut cependant modifier la liste de valeurs en associant explicitement une valeur à un ou plusieurs champs, en
utilisant la syntaxe enum nom_enumeration {nom_champ1, ..., nom_champX = val_champX, ...}.
Ex. : enum jours {lun=1, mar, jeu=4, ven, sam}; // mar vaut 2, ven vaut 5, sam 6
[Link] Déclaration
Une fois l’union définie, on peut utiliser directement son nom pour réaliser la déclaration d’une variable du type
uni, suivant la syntaxe union nom_union nom_variable_union.
Ex. : Une variable du type uni nombre.
union nombre nb1;
[Link] Affectation
L’affectation d’un champ d’une variable union s’opère comme pour une structure, en accédant directement au
champ dont on veut modifier la valeur.
Ex. : nb1.i = 0x1234; // affectation de 0x1234 au champ i
44 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
L’affectation d’une variable union en tant qu’entité à part entière ne peut être réalisée qu’en utilisant une autre
variable union de même type, déjà initialisée.
Ex. : union nombre nb2; // déclaration d'une 2nde variable union
nb2 = nb1; // affectation globale
[Link] Utilisation
L’utilisation d’un champ d’une variable union s’opère comme pour une structure.
Ex. : printf("%x\n", nb1.i); // affichage de 0x1234
printf("%x\n", nb1.c); // affichage de 0x34
L’utilisation d’une variable union en tant qu’entité se réalise exactement comme pour une structure.
[Link] Initialisation
L’initialisation d’une variable union consiste à initialiser sa valeur, suivant la syntaxe union nom_union
nom_variable_union = {valeur}.
Chaque champ de bits étant nécessairement de type entier, cela n’est pas obligatoire de le préciser. Il est possible
aussi de laisser 1 ou plusieurs bits inutilisés.
La taille maximale de la structure ou de l’union ainsi utilisée est de 32 bits.
Ex. : Une structure contenant 3 champs de bits d’une longueur totale de 16 bits.
struct bufferIO {
unsigned int commande :5; // un champ de bits non-signé de 5 bits
unsigned adresse :4; // un champ de bits non-signé de 4 bits
int :1 // 1 bit inutilisé
int donnees :6; // un champ de bits entier de 6 bits
};
[Link] Utilisation
L’utilisation d’un champ de bits d’une variable structure / union s’opère comme un champ « classique ».
45 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Les types synonymes peuvent être notamment utilisés avec les structures, et permettent de simplifier ainsi la
déclaration de variables structure.
Ex. : struct personne { // définition d'une structure
int age;
char initiale;
};
typedef struct personne tPersonne; // définition du nouveau type tPersonne
// on peut aussi écrire : typedef struct personne personne;
int main(void)
{
tPersonne eleve7; // tPersonne et struct personne sont utilisables
}
int main(void)
{
personne eleve7; // seul personne est un type utilisable
}
Nb : Il est possible de définir une structure ayant un champ du type de la structure elle-même.
Ex. : typedef struct personne {
int age;
char initiale;
struct personne parents[2];
} personne;
Un type de variable complexe constitue un nouveau type qui est utilisable exactement comme les types de variables
simples connus, dans tous les cas où ceux-ci peuvent être mis en œuvre : déclaration de variable, de fonction, passage
de paramètre, valeur renvoyée par une fonction au programme appelant, …
46 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
La possibilité pour le langage C de manipuler des flux d’entrées/sorties sur fichiers permet de sauvegarder des
données et de lire des données. Cela constitue ainsi un moyen pour un programme de communiquer avec le système
d’exploitation sur lequel il est exécuté.
6.1 INTRODUCTION
Un flux sur fichier correspond à l’action de lire ou d’écrire un fichier sur une mémoire de masse (disque dur,
disquette, cédérom, etc.).
On parle de flux d’entrée, lorsque les données « entrent » dans le programme, c’est-à-dire lorsque le programme lit
un fichier ; on parle de flux de sortie lorsque les données « sortent » du programme, c’est-à-dire lorsque le programme
écrit un fichier.
Accédé via un programme, un fichier est vu alors comme une suite d’octets, le dernier étant EOF 1.
On peut distinguer deux types de fichiers :
S fichier à accès séquentiel : les octets du fichier sont d’un seul bloc (pas d’octet vide ou appartenant à un autre
fichier), on accède à n’importe quel octet du fichier en se basant sur le tout premier, on ne peut pas effacer un
octet dans ce fichier, on peut en revanche tronquer la fin du fichier, ou bien rajouter un octet à la fin ;
S fichier à accès direct : les octets du fichiers peuvent être discontinus (octet vide possible), on peut accéder
directement à n’importe quel octet du fichier, on peut modifier ou détruire n’importe quel octet.
Les fonctions standard du C permettant de manipuler et gérer des fichiers sont des fonctions à accès séquentiel.
Pour pouvoir lire ou écrire un fichier, il faut donc déclarer un pointeur sur fichier avec le type déclaratif FILE 2,
contenu dans la bibliothèque stdio.h, selon la syntaxe FILE *nom_ptr_fichier.
Ex. : FILE *monfichier; // déclaration d'un pointeur sur fichier
6.2.2 Ouverture
L’ouverture du fichier, qui correspond à la création du flux, est nécessaire avant de pouvoir lire ou écrire un fichier.
Cette opération consiste à initialiser le pointeur sur le fichier désiré au sein du système d’exploitation, ainsi qu’à
définir la manière dont on veut accéder à ce fichier (lecture / écriture / mise à jour / etc.).
Pour ouvrir un fichier et réserver l’espace mémoire nécessaire à son exploitation, on utilise la fonction fopen() 3
suivant la syntaxe nom_ptr_fichier = fopen("nom_fichier", "mode_d_ouverture").
1
EOF : End Of File (fin du fichier).
2
Les majuscules sont importantes.
3
Comportement identique à la fonction malloc(), chargée de réserver l’espace mémoire pour un pointeur (cf. [Link]).
47 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
6.2.3 Fermeture
La fermeture du fichier, nécessaire lorsque le fichier ne doit plus être utilisé, s’opère avec la fonction fclose()
suivant la syntaxe fclose(nom_ptr_fichier).
Ex. : fclose(monfichier);
Ainsi, l’espace mémoire précédemment réservé est libéré, et les données éventuellement stockées en mémoire
tampon sont alors lues ou écrites physiquement.
Les différentes fonctions disponibles pour lire un fichier sont les suivantes :
S getc(), syntaxe variable_entière = getc(nom_ptr_fichier) : lit l’octet courant (qui peut être
assimilé à un caractère pour les fichiers texte), et le retourne sous format entier ;
S fgetc() : identique à getc() ;
S getw(), syntaxe variable_entière = getw(nom_ptr_fichier) : lit l’entier courant, et le renvoie ;
S fgets(), syntaxe fgets(chaine, nombre_caractères, nom_ptr_fichier) : lit le nombre de
caractères spécifié abaissé de 1 (caractère fin de chaîne), et les stocke dans une chaîne en ajoutant le caractère
fin de chaîne final ;
S fread(), syntaxe fread(ptr_dest, taille_bloc, nb_blocs, nom_ptr_fichier) : lit nb_blocs
de taille_bloc octets, et les stocke successivement dans le pointeur de destination ;
S fscanf(), syntaxe fscanf(nom_ptr_fichier, format, &variable1, &variable2, ...) : lit les
différents formats successifs (qui peuvent se présenter sous la forme texte+format), et les stocke dans l’ordre
dans les différentes variables spécifiées.
48 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
Les différentes fonctions disponibles pour lire un fichier sont les suivantes :
S putc(), syntaxe putc(caractère, nom_ptr_fichier) : écrit un caractère ;
S fputc : identique à putc() ;
S putw(), syntaxe putw(entier, nom_ptr_fichier) : écrit un entier ;
S fputs(), syntaxe fputs(chaine, nom_ptr_fichier) : écrit une chaîne (le caractère fin de chaîne n’est
pas écrit) ;
S fwrite(), syntaxe fwrite(ptr_src, taille_bloc, nb_blocs, nom_ptr_fichier) : écrit
nb_blocs de taille_bloc octets lus à partir du pointeur source ;
S fprintf(), syntaxe fprintf(nom_ptr_fichier, format, variable1, variable2, ...) : écrit
successivement les variables spécifiées selon le format (qui peut se présenté sous la forme texte+format).
Les flux d’entrées/sorties peuvent être opérés sur d’autres éléments que des fichiers. Notamment, l’interfaçage de
l’application avec le système d’exploitation utilise des flux d’entrées/sorties appelés flux d’entrées/sorties standard :
S entrée standard : par défaut le clavier, appelé stdin ;
S sortie standard : par défaut la console (l’écran), appelé stdout.
On peut ainsi noter que la fonction fprintf(), appliquée au flux de sortie standard (stdout), équivaut à la fonction
printf() : fprintf(stdout, format, variable1, variable2, ...) ; on écrit les variables dans le flux de
sortie, c’est-à-dire sur l’écran.
De la même manière, la fonction fscanf(), appliquée au flux d’entrée standard (stdin), équivaut à la fonction
scanf() : fscanf(stdin, format, &variable1, &variable2, ...) ; on lit les variables à partir du flux
d’entrée, c’est-à-dire le clavier.
49 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
A MOTS-CLEFS
Les mots-clefs sont réservés, donc inutilisables comme noms de variable ou de fonction 1.
A.2 CATÉGORIES
A.2.1 Types de variables
char Caractère sur 1 octet (standard ASCII étendu).
double Réel sur 8 octets.
FILE Pointeur sur fichier.
float Réel sur 4 octets.
int Entier sur 4 octets.
short Entier sur 2 octets.
void Type « vide » (utilisé pour les déclarations de fonctions uniquement).
1
Même si le langage C distingue la casse, il est déconseillé d’utiliser un nom, même avec une casse distincte, proche d’un mot-clef existant.
50 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
register
Variable stockée dans l’un des registres du processeur, et dont les accès sont ainsi optimisés (Nb :
amélioration des performances de l’application non garantie).
Type numérique signé, utilisant le bit de poids fort en guise de signe (0 : positif, 1 : négatif) ;
signed
échelle de valeurs centrée autour de 0 (types numériques uniquement, cf. unsigned) – par défaut.
static Variable locale dont la valeur est conservée entre deux appels de la fonction (cf. auto).
Type numérique non signé, utilisant tous les bits pour la valeur ; début de l’échelle de valeurs à
unsigned
partir de 0 (types numériques uniquement, cf. signed).
Variable sauvegardée uniquement en mémoire vive, et qui n’est jamais bufferisée ; sa valeur
volatile instantanée est ainsi assurée lors de l’accès par diverses fonctions, lorsque celles-ci sont
susceptibles d’en modifier la valeur et que le programme ne peut le prévoir à la compilation.
A.2.5 Autres
goto Branchement à une étiquette (désuet et déconseillé).
NULL Pointeur nul (pas d’espace mémoire réservé).
sizeof
Renvoi de la taille en octets occupée par un type donné, ou par une variable (i.e. le type auquel elle
appartient).
51 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
1
Pour un nombre réel noté ±x,y (x : partie entière, y : partie décimale) de 32 bits, celui-ci est ramené à une notation binaire en notation
scientifique (exposant) sous la forme ±1,zE±t et on le décompose ainsi : signe 1 bit, mantisse 23 bits (z), exposant 8 bits (t) ; avec les valeurs
d’exposants extrêmes réservées pour symboliser le nombre 0 (exposant = 0) et l’infini (exposant = 255), donc sur la plage [–126 ; +127], on
trouve : ±1x2-126 b ±1,17.10-38 (min) et ±(2x2+127–1) b ±3,40.10+38 (max).
2
64 bits : signe 1 bit, mantisse 52 bits, exposant 11 bits ; avec les valeurs d’exposants extrêmes réservées pour symboliser 0 (exposant = 0) et
l’infini (exposant = 2047), donc sur la plage [–1022 ; +1023], on trouve : ±1x2–1022 b ±2,23.10–308 (min) et ±(2x2+1023–1) b ±1,80.10+308 (max).
52 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
C DÉLIMITEURS ET OPÉRATEURS
C.1 DÉLIMITEURS
;
Marque la fin d’une ligne d’instruction ou d’une déclaration (typiquement, de toute ligne incluse dans
un bloc de code).
, Sépare deux éléments d’une liste.
" " Délimite une chaîne de caractères.
' ' Encadre un caractère.
( ) Encadre une liste de paramètres.
{ } Délimite un bloc d’instructions, ou une liste de valeurs d’initialisation.
[ ] Encadre la taille ou l’indice d’un tableau.
C.2 OPÉRATEURS
On distingue trois types d’opérateurs :
S unaire : 1 argument (1), syntaxe opérateur opérande ;
S binaire : 2 arguments (2), syntaxe opérande1 opérateur opérande2 ;
S ternaire : 3 arguments (3), syntaxe opérande1 opérateurA opérande2 opérateurB opérande3.
53 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
|
&&
||
? : (3)
= *= /= %= += -= <<= >>= &= ^= |=
,
54 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
D RÉFÉRENCE DU LANGAGE
1
En janvier 2006.
2
Cette remarque est aussi vraie pour d’autres langages, tels le C++ (la STL), le C# (le framework .Net), Java (API Java), etc..
55 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
56 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
D.3 LE PRÉPROCESSEUR
Le préprocesseur est un programme spécifique fourni par le compilateur qui analyse le code source C et effectue
des modifications sur son contenu avant qu’il soit compilé. Tous les ordres de modification du code, appelés
directives, commencent par le symbole #.
# Directive nulle.
#define Définition d’une macro (constante ou fonction) (cf. #undef).
Compilation conditionnelle sur expression dans une compilation conditionnelle inversée –
#elif
contraction de #else et de #if (cf. #if, #else, #endif, #ifdef et #ifndef).
#else Compilation conditionnelle inversée (cf. #if, #elif, #endif, #ifdef et #ifndef).
#endif Fin d’une compilation conditionnelle (cf. #if, #elif, #else, #ifdef et #ifndef).
#error Émission d’un message d’erreur.
#if Compilation conditionnelle sur expression (cf. #elif, #else, #endif, #ifdef et #ifndef).
Compilation conditionnelle sur existence de la définition d’une macro (cf. #if, #elif, #else,
#ifdef
#endif et #ifndef).
Compilation conditionnelle sur absence de la définition d’une macro (cf. #if, #elif, #else,
#ifndef
#endif et #ifdef).
#include Inclusion du contenu d’un fichier (recopie) dans le fichier courant.
#line Changement du numéro de la ligne courante.
57 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
#pragma
Non-prise en compte des directives précisées si le compilateur ne les comprend pas (plutôt que
d’afficher des messages d’erreurs de compilation).
#undef Annulation d’une définition de macro (cf. #define).
defined() Opérateur de test de la définition d’une macro (s’utilise exclusivement avec #if et #elif).
__DATE__ Chaîne représentant la date courante (macro prédéfinie).
__FILE__ Nom du fichier source courant (macro prédéfinie).
__LINE__ Numéro de la ligne courante (macro prédéfinie).
__STDC__ Test des qualités ISO du compilateur (macro prédéfinie).
__TIME__ Chaîne représentant l’heure courante (macro prédéfinie).
#define PI 3.14
#define testrayon(r) (r>100 ? 0 : 1)
int main(void)
{
float rayon;
float circonference
if (testrayon(rayon)) {
circonference = 2 * PI * rayon;
printf("circonference : %f\n", circonference);
}
else {
printf("rayon trop grand\n");
}
return 0;
}
int main(void)
{
float rayon;
float circonference
return 0;
}
L’usage du préprocesseur mérite énormément d’attention. Il constitue un outil puissant mais est une source d’erreurs
difficiles à détecter, puisque les modifications sont réalisées avant la compilation et qu’elles ne subissent donc aucun
contrôle.
58 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
E LE CODE ASCII
Le code ASCII, créé dans les années 60, permet de coder numériquement un caractère afin de pouvoir être manipulé
par l’ordinateur. Il utilise dans sa version standard 7 bits, et permet donc de coder 128 caractères.
Les 32 premiers caractères sont des caractères de contrôle, donc non-affichables, qui servaient anciennement aux
téléscripteurs comme codes de mise en page ; beaucoup sont maintenant désuets.
dec hex caractère dec hex caractère dec hex caractère dec hex caractère
0 00 NUL (null) 32 20 <space> 64 40 @ 96 60 `
1 01 SOH (start of heading) 33 21 ! 65 41 A 97 61 a
2 02 STX (start of text) 34 22 " 66 42 B 98 62 b
3 03 ETX (end of text) 35 23 # 67 43 C 99 63 c
4 04 EOT (end of transmission) 36 24 $ 68 44 D 100 64 d
5 05 ENQ (enquiry) 37 25 % 69 45 E 101 65 e
6 06 ACK (acknowledge) 38 26 & 70 46 F 102 66 f
7 07 BEL (bell), '\a' 39 27 ' 71 47 G 103 67 g
8 08 BS (backspace), '\b' 40 28 ( 72 48 H 104 68 h
9 09 TAB (horizontal tab), '\t' 41 29 ) 73 49 I 105 69 i
10 0A LF (line feed = new line), '\n' 42 2A * 74 4A J 106 6A j
11 0B VT (vertical tab) 43 2B + 75 4B K 107 6B k
12 0C FF (form feed), '\f' 44 2C ´ 76 4C L 108 6C l
13 0D CR (carriage return), '\r' 45 2D – 77 4D M 109 6D m
14 0E SO (shift out) 46 2E . 78 4E N 110 6E n
15 0F SI (shift in) 47 2F / 79 4F O 111 6F o
16 10 DLE (data link escape) 48 30 0 80 50 P 112 70 p
17 11 DC1 (device control 1) 49 31 1 81 51 Q 113 71 q
18 12 DC2 (device control 2) 50 32 2 82 52 R 114 72 r
19 13 DC3 (device control 3) 51 33 3 83 53 S 115 73 s
20 14 DC4 (device control 4) 52 34 4 84 54 T 116 74 t
21 15 NAK (negativ aknowledge) 53 35 5 85 55 U 117 75 u
22 16 SYN (synchronous idle) 54 36 6 86 56 V 118 76 v
23 17 ETB (end of transmission bloc) 55 37 7 87 57 W 119 77 w
24 18 CAN (cancel) 56 38 8 88 58 X 120 78 x
25 19 EM (end of medium) 57 39 9 89 59 Y 121 79 y
26 1A SUB (substitute) 58 3A : 90 5A Z 122 7A z
27 1B ESC (escape) 59 3B ; 91 5B [ 123 7B {
28 1C FS (file separator) 60 3C < 92 5C \ 124 7C |
29 1D GS (group separator) 61 3D = 93 5D ] 125 7D }
30 1E RS (record separator) 62 3E > 94 5E ^ 126 7E ~
31 1F US (unit separator) 63 3F ? 95 5F _ 127 7F DEL
Par la suite, on a créé le code ASCII étendu afin d’inclure les caractères accentués. On a pour cela utilisé un 8ème bit
pour le codage, portant ainsi la table à 256 caractères différents 1.
Nb : Au début des années 90, le standard Unicode 2 a été créé. Reprenant les mêmes principes que le code ASCII,
mais en utilisant 16 bits 3, ce code permet ainsi de pouvoir coder numériquement la quasi-totalité des alphabets
existants, notamment les alphabets cyrillique, grec, arabe et hébreu. Il a été conçu avec une compatibilité totale avec le
code ASCII existant, id est, les 256 premiers caractères Unicode sont les 256 caractères du code ASCII.
1
L’usage des multiples de 8 bits s’étant démocratisé, l’appellation « code ASCII » fait aujourd’hui référence au code ASCII étendu à 8 bits.
2
Listes des codes Unicode classées par thème disponible sur [Link]
3
En janvier 2008, dans sa version 5.0.0, le standard Unicode utilise maintenant 20 bits.
59 / 60
Langages > Langage C > Cours v1.1.11.0 – 16/02/2010
F BIBLIOGRAPHIE
60 / 60