le langage c - Exercices corriges
TD. TP. Total. ECTS. UE fondamentales. Mathématiques 1. 97,5. 97,5. 8 ......
Compilation : principes généraux, analyse lexicale (TP avec Lex), analyse ...
part of the document
issance, il est nécessaire d'avoir une bonne expérience préalable de la programmation pour l'utiliser à bon escient. L'étudiant idéal devrait maîtriser un langage structuré de type PASCAL et un langage assembleur. Il est fortement déconseillé à un débutant de même qu'il est déconseillé de mettre une FERRARI dans une auto école....
UNIX et C sont très intimement liés : c'est pourquoi un utilisateur d'une machine fonctionnant sous UNIX est souvent amené à utiliser et connaître le langage C.
Nous nous sommes attachés à présenter les concepts normalisés du langage, issus de la "bible" du langage C "The C programming langage" (version anglaise) de Kernighan et Ritchie dont nous conseillons l'acquisition à tout lecteur désireux d'approfondir ses connaissances en C et en programmation structurée. Sa première version, datant de 1978, n'en présente pas les tous derniers développements. Une deuxième édition, conforme à la norme ANSI X3J11 (1988) qui impose des modifications dans la définition des fonctions et qui a permis une définition rigoureuse des bibliothèques C, a été publiée. Nous en avons tenu compte dans la totalité des exemples présentés.
Cette édition a été enrichie : ajout des outils de développement, d'exercices corrigés, gestion de l'allocation dynamique de listes chaînées, fonctions à nombre d'arguments variables, etc.
Le chapitre 1 s'intitule "Démarrer en C" : il présente très sommairement les concepts de base de façon à permettre au lecteur d'écrire rapidement un petit programme en C et de comprendre les instructions de base du langage.
Dans le chapitre 2 sont présentés la philosophie du langage, les déclarations de types des variables et les opérateurs binaires du langage.
Les structures de contrôle (boucles et tests) sont présentées au chapitre 3.
Dans le chapitre 4, on trouvera la gestion des classes de mémorisation des variables, les règles d'initialisation des variables ainsi que les principes d'utilisation du préprocesseur.
Les fonctions et les pointeurs sont présentées au chapitre 5.
Au chapitre 6 sont présentées les concepts orientés objet de définition de structures de données.
Les instructions d'entrée/sortie de bas niveau sont présentées au chapitre 7.
Les instructions d'entrées/sorties disponibles dans les bibliothèques sont présentées au chapitre 8.
Les outils de développement standards sous UNIX sont présentés au chapitre 9.
On trouvera en annexe la description des principales bibliothèques utilisées en programmation C, une description concise des apports de la norme ANSI, les corrigés des exercices proposés, un index.
Table des matières
TM \o1. DEMARRER EN C RENVOIPAGE _Toc8703291 \h 10
1.1 Généralités sur la structure des programmes RENVOIPAGE _Toc8703292 \h 10
1.2 Fonctions élémentaires d'Entrée-Sortie RENVOIPAGE _Toc8703293 \h 11
1.2.1 Entrées/sorties sur des caractères sans spécification de format RENVOIPAGE _Toc8703294 \h 11
1.2.2 Entrées/sorties avec spécification de format RENVOIPAGE _Toc8703295 \h 12
1.3 3 Structures de contrôle élémentaires RENVOIPAGE _Toc8703296 \h 12
1.3.1 Boucles RENVOIPAGE _Toc8703297 \h 12
1.3.2 Exemples de programme en C RENVOIPAGE _Toc8703298 \h 13
1.3.3 Tests RENVOIPAGE _Toc8703299 \h 14
1.3.4 Utilisation des fonctions RENVOIPAGE _Toc8703300 \h 15
2. TYPES, OPERATEURS ET EXPRESSIONS RENVOIPAGE _Toc8703301 \h 17
2.1 Esprit, règles de bonne programmation et maximes RENVOIPAGE _Toc8703302 \h 17
2.1.1 Esprit du langage C RENVOIPAGE _Toc8703303 \h 17
2.1.2 Règles générales de programmation et de portabilité RENVOIPAGE _Toc8703304 \h 18
2.1.3 Les maximes RENVOIPAGE _Toc8703305 \h 18
2.2 Principes généraux du langage RENVOIPAGE _Toc8703306 \h 19
2.2.1 Expressions et instructions RENVOIPAGE _Toc8703307 \h 19
2.2.2 Mots clés du langage C RENVOIPAGE _Toc8703308 \h 20
2.3 Variables RENVOIPAGE _Toc8703309 \h 21
2.3.1 Identificateur, type et classe d'allocation RENVOIPAGE _Toc8703310 \h 21
2.3.2 Définition et déclaration RENVOIPAGE _Toc8703311 \h 21
2.3.3 Règles d'utilisation des variables RENVOIPAGE _Toc8703312 \h 22
2.3.4 Accès aux variables RENVOIPAGE _Toc8703313 \h 22
2.3.5 Lvaleur, Rvaleur et affectation RENVOIPAGE _Toc8703314 \h 23
2.4 Types RENVOIPAGE _Toc8703315 \h 23
2.4.1 Types scalaires RENVOIPAGE _Toc8703316 \h 25
2.4.2 Qualificatif RENVOIPAGE _Toc8703317 \h 25
2.4.3 Tableau RENVOIPAGE _Toc8703318 \h 27
2.4.4 Conversions de type implicites RENVOIPAGE _Toc8703319 \h 28
2.4.5 Conversion de type explicite RENVOIPAGE _Toc8703320 \h 29
2.4.6 Diagramme de définition des variables RENVOIPAGE _Toc8703321 \h 30
2.5 Variables qualifiées constantes RENVOIPAGE _Toc8703322 \h 31
2.5.1 Variables qualifiées constantes entières RENVOIPAGE _Toc8703323 \h 32
2.5.2 Variables qualifiées constantes réelles RENVOIPAGE _Toc8703324 \h 32
2.5.3 Variables qualifiées constantes de type caractère RENVOIPAGE _Toc8703325 \h 32
2.5.4 Variables qualifiées constantes de type chaîne de caractères RENVOIPAGE _Toc8703326 \h 33
2.6 Opérateurs RENVOIPAGE _Toc8703327 \h 34
2.6.1 Opérateurs binaires RENVOIPAGE _Toc8703328 \h 34
2.6.2 Opérateurs d'incrémentation et décrémentation RENVOIPAGE _Toc8703329 \h 35
2.6.3 Opérateurs composés RENVOIPAGE _Toc8703330 \h 37
2.6.4 Opérateurs sur les champs de bits RENVOIPAGE _Toc8703331 \h 37
2.6.5 L'opérateur sizeof RENVOIPAGE _Toc8703332 \h 40
2.6.6 Calcul de l'espace mémoire occupé par un tableau RENVOIPAGE _Toc8703333 \h 41
2.7 Tableau Récapitulatif des opérateurs BINAIRES RENVOIPAGE _Toc8703334 \h 43
2.8 Priorité et règles d'associativité des opérateurs RENVOIPAGE _Toc8703335 \h 43
3. STRUCTURES DE CONTROLE RENVOIPAGE _Toc8703336 \h 45
3.1 Les tests RENVOIPAGE _Toc8703337 \h 45
3.1.1 L'instruction if else RENVOIPAGE _Toc8703338 \h 45
3.1.2 L'opérateur ternaire RENVOIPAGE _Toc8703339 \h 45
3.1.3 La construction else if RENVOIPAGE _Toc8703340 \h 46
3.2 Les boucles RENVOIPAGE _Toc8703341 \h 46
3.2.1 La boucle while RENVOIPAGE _Toc8703342 \h 47
3.2.2 La boucle for RENVOIPAGE _Toc8703343 \h 47
3.2.3 La boucle do while RENVOIPAGE _Toc8703344 \h 49
3.3 Les instructions de rupture de sequence RENVOIPAGE _Toc8703345 \h 49
3.3.1 L'instruction break RENVOIPAGE _Toc8703346 \h 50
3.3.2 L'instruction continue RENVOIPAGE _Toc8703347 \h 51
3.3.3 L'instruction goto RENVOIPAGE _Toc8703348 \h 51
3.4 Branchement multiple : l'instruction switch RENVOIPAGE _Toc8703349 \h 52
4. Classes de mémorisation ET PRéPROCESSEUR RENVOIPAGE _Toc8703350 \h 54
4.1 Classes de mémorisation RENVOIPAGE _Toc8703351 \h 54
4.1.1 Variables globales RENVOIPAGE _Toc8703352 \h 54
4.1.2 Variables locales RENVOIPAGE _Toc8703353 \h 57
4.1.3 Variables automatiques RENVOIPAGE _Toc8703354 \h 58
4.1.4 Variables statiques RENVOIPAGE _Toc8703355 \h 58
4.1.5 Variables de type register RENVOIPAGE _Toc8703356 \h 60
4.2 Initialisations RENVOIPAGE _Toc8703357 \h 60
4.2.1 Variables statiques et variables externes RENVOIPAGE _Toc8703358 \h 60
4.2.2 Variables automatiques et registres RENVOIPAGE _Toc8703359 \h 60
4.2.3 Tableaux d'entiers ou de réels RENVOIPAGE _Toc8703360 \h 61
4.2.4 Initialisation de chaînes de caractères RENVOIPAGE _Toc8703361 \h 62
4.2.5 Chaînes de caractères et pointeurs RENVOIPAGE _Toc8703362 \h 63
4.2.6 Variables qualifiées RENVOIPAGE _Toc8703363 \h 64
4.3 Généralités sur le préprocesseur RENVOIPAGE _Toc8703364 \h 64
4.3.1 Inclusion de fichiers RENVOIPAGE _Toc8703365 \h 65
4.3.2 Généralités sur les macrodéfinitions RENVOIPAGE _Toc8703366 \h 65
4.3.3 Macro-définition de type constante RENVOIPAGE _Toc8703367 \h 65
4.3.4 Macrodéfinition de type fonction RENVOIPAGE _Toc8703368 \h 67
4.3.5 L'opérateur # RENVOIPAGE _Toc8703369 \h 69
4.3.6 Compilation conditionnelle RENVOIPAGE _Toc8703370 \h 71
4.3.7 Variable locales et préprocesseur RENVOIPAGE _Toc8703371 \h 72
5. FONCTIONS ET POINTEURS RENVOIPAGE _Toc8703372 \h 74
5.1 Généralités RENVOIPAGE _Toc8703373 \h 74
5.2 Règles d'utilisation des fonctions RENVOIPAGE _Toc8703374 \h 74
5.2.1 Définition RENVOIPAGE _Toc8703375 \h 74
5.2.2 Règles élémentaires d'utilisation RENVOIPAGE _Toc8703376 \h 76
5.2.3 Déclaration et prototypes RENVOIPAGE _Toc8703377 \h 76
5.2.4 Appel RENVOIPAGE _Toc8703378 \h 77
5.2.5 Le mot clé void RENVOIPAGE _Toc8703379 \h 78
5.2.6 Transmission du résultat RENVOIPAGE _Toc8703380 \h 79
5.2.7 Type du résultat RENVOIPAGE _Toc8703381 \h 79
5.2.8 Récursivité RENVOIPAGE _Toc8703382 \h 80
5.2.9 Modes de transmission des arguments RENVOIPAGE _Toc8703383 \h 81
5.2.10 Cas particulier des tableaux RENVOIPAGE _Toc8703384 \h 83
5.3 Pointeurs RENVOIPAGE _Toc8703385 \h 84
5.3.1 Définitions RENVOIPAGE _Toc8703386 \h 84
5.3.2 Définitions et déclarations RENVOIPAGE _Toc8703387 \h 86
5.3.3 Exemples de définitions RENVOIPAGE _Toc8703388 \h 86
5.4 Arguments de fonctions et pointeurs RENVOIPAGE _Toc8703389 \h 88
5.5 Tableaux et pointeurs RENVOIPAGE _Toc8703390 \h 91
5.5.1 Calcul d'adresse RENVOIPAGE _Toc8703391 \h 91
5.5.2 Correspondance tableau-pointeur RENVOIPAGE _Toc8703392 \h 91
5.5.3 Opérations sur les pointeurs RENVOIPAGE _Toc8703393 \h 96
5.5.4 Conversion de pointeurs RENVOIPAGE _Toc8703394 \h 96
5.5.5 Le pointeur générique RENVOIPAGE _Toc8703395 \h 97
5.6 Chaînes de caractères et pointeurs RENVOIPAGE _Toc8703396 \h 97
5.6.1 Règles d'initialisation RENVOIPAGE _Toc8703397 \h 97
5.6.2 Exemples d'initialisation RENVOIPAGE _Toc8703398 \h 98
5.6.3 Taille RENVOIPAGE _Toc8703399 \h 101
5.6.4 Recopie RENVOIPAGE _Toc8703400 \h 102
5.6.5 Comparaison RENVOIPAGE _Toc8703401 \h 102
5.6.6 Concaténation RENVOIPAGE _Toc8703402 \h 103
5.7 Quelques applications des pointeurs RENVOIPAGE _Toc8703403 \h 103
5.7.1 Transmission d'arguments à la fonction main RENVOIPAGE _Toc8703404 \h 103
5.7.2 Pointeur sur des tableaux et tableaux de pointeurs RENVOIPAGE _Toc8703405 \h 105
5.7.3 Transtypage et pointeurs RENVOIPAGE _Toc8703406 \h 108
5.7.4 Pointeurs sur des fonctions RENVOIPAGE _Toc8703407 \h 109
5.8 Allocation dynamique de la mémoire RENVOIPAGE _Toc8703408 \h 110
5.8.1 Allocation dynamique RENVOIPAGE _Toc8703409 \h 110
5.8.2 Libération RENVOIPAGE _Toc8703410 \h 110
5.9 Fonction à nombre d'arguments variable RENVOIPAGE _Toc8703411 \h 111
5.9.1 Principe général RENVOIPAGE _Toc8703412 \h 111
5.9.2 Gestion de la pile des arguments RENVOIPAGE _Toc8703413 \h 111
5.9.3 Le type va_list RENVOIPAGE _Toc8703414 \h 112
5.9.4 La fonction va_start RENVOIPAGE _Toc8703415 \h 112
5.9.5 La fonction va_arg RENVOIPAGE _Toc8703416 \h 112
5.9.6 La fonction va_end RENVOIPAGE _Toc8703417 \h 112
6. STRUCTURES DE DONNEES RENVOIPAGE _Toc8703418 \h 115
6.1 Structures RENVOIPAGE _Toc8703419 \h 115
6.1.1 Définition RENVOIPAGE _Toc8703420 \h 115
6.1.2 Déclaration et définition RENVOIPAGE _Toc8703421 \h 115
6.1.3 Règles d'utilisations RENVOIPAGE _Toc8703422 \h 116
6.1.4 Accès aux champs d'une structure : l'opérateur de sélection de membre RENVOIPAGE _Toc8703423 \h 117
6.1.5 Pointeurs sur des structures : l'opérateur -> RENVOIPAGE _Toc8703424 \h 117
6.1.6 Associativité des opérateurs * ->++ -- () [ ] RENVOIPAGE _Toc8703425 \h 119
6.1.7 Pointeurs, structures et fonctions RENVOIPAGE _Toc8703426 \h 121
6.1.8 Tableau de structures RENVOIPAGE _Toc8703427 \h 124
6.1.9 Assignation de structure RENVOIPAGE _Toc8703428 \h 125
6.1.10 Champs de bits RENVOIPAGE _Toc8703429 \h 126
6.2 Unions RENVOIPAGE _Toc8703430 \h 129
6.2.1 Déclaration et définition RENVOIPAGE _Toc8703431 \h 129
6.2.2 Structure et union RENVOIPAGE _Toc8703432 \h 131
6.3 Type énuméré RENVOIPAGE _Toc8703433 \h 131
6.4 L'instruction typedef RENVOIPAGE _Toc8703434 \h 132
6.4.1 Type synonyme RENVOIPAGE _Toc8703435 \h 132
6.4.2 Définition d'un nom de type RENVOIPAGE _Toc8703436 \h 133
6.5 Rappels sur les structures de données classiques RENVOIPAGE _Toc8703437 \h 133
6.5.1 Tableau - pointeur - liste - table file RENVOIPAGE _Toc8703438 \h 133
6.5.2 Liste chaînée selon un algorithme FIFO RENVOIPAGE _Toc8703439 \h 134
6.5.3 Liste chaînée selon un algorithme LIFO RENVOIPAGE _Toc8703440 \h 136
6.5.4 Liste doublement chaînée RENVOIPAGE _Toc8703441 \h 136
6.5.5 Piles RENVOIPAGE _Toc8703442 \h 137
6.5.6 Opérations sur les piles RENVOIPAGE _Toc8703443 \h 137
6.6 Exercice récapitulatif sur les listes RENVOIPAGE _Toc8703444 \h 138
7. ENTREES SORTIES DE BAS NIVEAU RENVOIPAGE _Toc8703445 \h 146
7.1 Cycle de vie d'un fichier RENVOIPAGE _Toc8703446 \h 146
7.2 Opérations de bas niveau sur les fichiers RENVOIPAGE _Toc8703447 \h 147
7.2.1 Opérations de bas niveau et fonctions de la bibliothèque C RENVOIPAGE _Toc8703448 \h 147
7.2.2 Attributs des fichiers RENVOIPAGE _Toc8703449 \h 147
7.2.3 Test des droits d'accès RENVOIPAGE _Toc8703450 \h 148
7.3 Modification des attributs RENVOIPAGE _Toc8703451 \h 148
7.3.1 Modification des droits d'accès RENVOIPAGE _Toc8703452 \h 148
7.3.2 Modification du propriétaire RENVOIPAGE _Toc8703453 \h 149
7.4 Création d'un inode associé à un fichier spécial RENVOIPAGE _Toc8703454 \h 149
7.5 Opérations sur les fichiers RENVOIPAGE _Toc8703455 \h 150
7.5.1 Descripteur de fichier RENVOIPAGE _Toc8703456 \h 150
7.5.2 Ouverture RENVOIPAGE _Toc8703457 \h 150
7.5.3 Création : appel creat RENVOIPAGE _Toc8703458 \h 151
7.5.4 Lecture et écriture RENVOIPAGE _Toc8703459 \h 151
7.5.5 Fermeture RENVOIPAGE _Toc8703460 \h 154
7.6 Déplacement dans un fichier RENVOIPAGE _Toc8703461 \h 155
7.7 Appels de commandes de l'interprète RENVOIPAGE _Toc8703462 \h 155
8. Fonctions d'ENTREES/SORTIES de la Bibliothèque C RENVOIPAGE _Toc8703463 \h 157
8.1 Bibliothèques associées aux entrées/sorties RENVOIPAGE _Toc8703464 \h 157
8.2 Généralités sur les entrées/sorties en mode bloc RENVOIPAGE _Toc8703465 \h 157
8.2.1 Fichiers standards RENVOIPAGE _Toc8703466 \h 158
8.2.2 Redirection RENVOIPAGE _Toc8703467 \h 158
8.2.3 Flots RENVOIPAGE _Toc8703468 \h 159
8.3 Le fichier stdio.h RENVOIPAGE _Toc8703469 \h 160
8.4 Entrées/sorties sur les fichiers standards RENVOIPAGE _Toc8703470 \h 160
8.4.1 Entrées/sorties en mode caractère RENVOIPAGE _Toc8703471 \h 161
8.4.2 Entrées/sorties avec spécification de format RENVOIPAGE _Toc8703472 \h 161
8.4.3 Lecture et écriture de lignes de caractères RENVOIPAGE _Toc8703473 \h 167
8.5 Entrées/sorties sur les fichiers ordinaires RENVOIPAGE _Toc8703474 \h 168
8.5.1 Ouverture RENVOIPAGE _Toc8703475 \h 169
8.5.2 Fermeture RENVOIPAGE _Toc8703476 \h 170
8.5.3 Entrées/sorties en mode caractère RENVOIPAGE _Toc8703477 \h 171
8.5.4 Entrées/sorties avec spécification de format RENVOIPAGE _Toc8703478 \h 172
8.5.5 Ecriture et lecture de lignes de caractères RENVOIPAGE _Toc8703479 \h 173
8.5.6 Ecriture et lecture d'items RENVOIPAGE _Toc8703480 \h 174
8.5.7 Gestion du tampon d'entrée/sortie RENVOIPAGE _Toc8703481 \h 175
8.5.8 Accès direct à l'information RENVOIPAGE _Toc8703482 \h 177
8.6 Lecture et écriture en mémoire RENVOIPAGE _Toc8703483 \h 180
8.7 Récapitulation des instructions d'entrée/sortie RENVOIPAGE _Toc8703484 \h 181
8.8 Les fonctions Qsort et Bsearch RENVOIPAGE _Toc8703485 \h 182
8.9 Exercice récapitulatif RENVOIPAGE _Toc8703486 \h 182
8.9.1 Enoncé RENVOIPAGE _Toc8703487 \h 183
8.9.2 Programme RENVOIPAGE _Toc8703488 \h 183
9. LES OUTILS DE DEVELOPPEMENT SOUS UNIX RENVOIPAGE _Toc8703489 \h 188
9.1 La chaîne de développement RENVOIPAGE _Toc8703490 \h 188
9.2 Génération d'un programme exécutable RENVOIPAGE _Toc8703491 \h 188
9.2.1 Cycle de développement RENVOIPAGE _Toc8703492 \h 188
9.2.2 Compilation et édition de lien RENVOIPAGE _Toc8703493 \h 189
9.2.3 Langage machine RENVOIPAGE _Toc8703494 \h 189
9.2.4 Assembleur RENVOIPAGE _Toc8703495 \h 189
9.3 Mise au point des programmes sources RENVOIPAGE _Toc8703496 \h 190
9.3.1 Editeurs de textes RENVOIPAGE _Toc8703497 \h 190
9.3.2 Outil de mise en forme RENVOIPAGE _Toc8703498 \h 190
9.3.3 Analyseur syntaxique RENVOIPAGE _Toc8703499 \h 190
9.4 Le compilateur C RENVOIPAGE _Toc8703500 \h 190
9.4.1 Appel RENVOIPAGE _Toc8703501 \h 191
9.4.2 Quelques options de compilation RENVOIPAGE _Toc8703502 \h 192
9.5 Edition de lien RENVOIPAGE _Toc8703503 \h 194
9.5.1 Bibliothèques RENVOIPAGE _Toc8703504 \h 194
9.5.2 Edition de lien statique et édition de lien dynamique RENVOIPAGE _Toc8703505 \h 195
9.5.3 Appel RENVOIPAGE _Toc8703506 \h 195
9.5.4 Opérations sur les bibliothèques RENVOIPAGE _Toc8703507 \h 196
9.5.5 Exemples RENVOIPAGE _Toc8703508 \h 197
9.6 Analyse des programmes RENVOIPAGE _Toc8703509 \h 199
9.6.1 Structure d'un fichier exécutable RENVOIPAGE _Toc8703510 \h 199
9.6.2 Formats des fichiers exécutables RENVOIPAGE _Toc8703511 \h 200
9.6.3 Commandes associées RENVOIPAGE _Toc8703512 \h 200
9.6.4 Dépendances RENVOIPAGE _Toc8703513 \h 203
9.6.5 Profileurs RENVOIPAGE _Toc8703514 \h 205
9.6.6 Débogueurs RENVOIPAGE _Toc8703515 \h 210
9.7 Outils de génie logiciel RENVOIPAGE _Toc8703516 \h 210
9.7.1 La commande make RENVOIPAGE _Toc8703517 \h 210
9.7.2 SCCS RENVOIPAGE _Toc8703518 \h 218
9.8 Autres compilateurs RENVOIPAGE _Toc8703519 \h 220
9.9 Environnement de développement RENVOIPAGE _Toc8703520 \h 220
9.9.1 Interprètes de commandes RENVOIPAGE _Toc8703521 \h 220
9.9.2 Environnement de l'utilisateur RENVOIPAGE _Toc8703522 \h 221
9.10 Exercice sur l'Environnement de programmation RENVOIPAGE _Toc8703523 \h 221
9.11 La commande Make RENVOIPAGE _Toc8703524 \h 226
10. ANNEXE 1 : principaux prototypes et fichiers en tête associés RENVOIPAGE _Toc8703525 \h 227
10.1 ctype.h RENVOIPAGE _Toc8703526 \h 227
10.2 fcntl.H RENVOIPAGE _Toc8703527 \h 227
10.3 limits.h RENVOIPAGE _Toc8703528 \h 228
10.4 math.h RENVOIPAGE _Toc8703529 \h 228
10.5 stdio.h RENVOIPAGE _Toc8703530 \h 230
10.6 stdlib.h RENVOIPAGE _Toc8703531 \h 232
10.7 string.h RENVOIPAGE _Toc8703532 \h 234
10.8 time.h RENVOIPAGE _Toc8703533 \h 235
11. ANNEXE 2 : APPORTS DE LA NORME ANSI RENVOIPAGE _Toc8703534 \h 237
12. Annexe 3 : Rappels sur la représentation de l'information RENVOIPAGE _Toc8703535 \h 242
12.1 Représentation binaire et hexadécimale RENVOIPAGE _Toc8703536 \h 242
12.2 Données alphanumériques RENVOIPAGE _Toc8703537 \h 243
12.3 Nombres entiers RENVOIPAGE _Toc8703538 \h 245
12.4 Nombres réels RENVOIPAGE _Toc8703539 \h 246
12.5 Instructions RENVOIPAGE _Toc8703540 \h 251
12.6 Images RENVOIPAGE _Toc8703541 \h 251
13. Corrigé des exercices RENVOIPAGE _Toc8703542 \h 252
13.1.1 Chapitre 1 RENVOIPAGE _Toc8703543 \h 252
13.1.2 Chapitre 2 RENVOIPAGE _Toc8703544 \h 253
13.1.3 Chapitre 3 RENVOIPAGE _Toc8703545 \h 255
13.1.4 Chapitre 5 RENVOIPAGE _Toc8703546 \h 263
13.1.5 Chapitre 6 RENVOIPAGE _Toc8703547 \h 282
13.1.6 Chapitre 8 RENVOIPAGE _Toc8703548 \h 292
14. INDEX RENVOIPAGE _Toc8703549 \h 297
DEMARRER EN C
Ce chapitre est une présentation très sommaire en langage C de concepts de base de programmation supposés connus du lecteur dans un autre langage : déclarations de types prédéfinis, structures de contrôle. Tous ces concepts sont nécessaires dans les exemples présentés. Ils sont approfondis dans les chapitres ultérieurs.
La sémantique des instructions ainsi que les algorithmes utilisés sont quelquefois présentés en utilisant le métalangage correspondant en programmation structurée suivant :
:= représente l'affectation
Les mots clés sont les suivants :
si....alors....sinon...is
Tant que.... faire
Généralités sur la structure des programmes
Les différents types d'instructions du langage C sont les suivants :
définition et déclaration des variables et des fonctions,
directives de compilation pour le préprocesseur.
Une instruction est une expressionex "expression" suivie d'un terminateur obligatoire qui est le ;ex ";".
Une instruction est simple ou composée :
une instruction simpleex "instruction simple" est une instruction unique.
une instruction composéeex "instruction composée" est un ensemble d'instructions simples regroupées dans un même bloc dont la structure a la forme :
{ /* marque de début de bloc */
instruction(s)
} /* marque de fin de bloc */
Cette structure est identique au bloc PASCAL
BEGIN... END
Les commentaires dans les programmes sont entre les délimiteurs /* et */.
Exemple : /* ceci est un commentaire */
Tout programme exécutable en C est constitué d'un ensemble de fonctions dont la fonction mainEX "main" au moins, qui est toujours la première à être exécutée. C'est l'identificateur permettant d'accéder à l'adresse de la première instruction à exécuter.
Tout appel de fonction a la forme suivante :
nom_fonction(arg1,..., argn)
Une liste d'arguments vide est autorisée.
La notion de programme principal, contrairement aux langages Fortran ou Pascal, n'existe pas en C. Toutefois, la fonction main a un rôle similaire.
Exemple
int main(void)
{printf("bonjour\n");}
Description : impression de la chaîne de caractères bonjour et passage à la ligne suivante (caractère spécial \n).
Le nom des variables, dont la déclaration préalable est obligatoire, est un identificateur formé d'au plus trente et un caractères alphanumériques.
Les types élémentaires prédéfinis les plus simples sont :
int entier
float réel flottant
char caractère
Une constante entière s'écrit 5 et une constante réelle 5.0.
Les autres types composés prédéfinis sont les types tableau, pointeur, structure et union.
Fonctions élémentaires d'Entrée-Sortie
Entrées/sorties sur des caractères sans spécification de formatex "entrées/sorties en mode caractère sans spécification de format"
Les opérations d'entrées/sorties sans spécification de format sont réalisées à l'aide de macrodéfinitions, contenus dans des fichiers entête du répertoire /usr/includeEX "/usr/include". Le préprocesseur, appelé par le compilateur C, permet d'inclure dans le programme source des prototypes et des constantes prédéfinies dans des fichiers entête (par exemple stdio.h) en utilisant la directive :
#includeex "#include"
Lecture
La fonction getcharex "getchar" réalise la saisie d'un caractère ensuite affecté à une variable de type entier :
Exemple : c = getchar();
Ecriture
La fonction putcharex "putchar" permet l'écriture du caractère contenu dans une variable :
Exemples
putchar(c); /* impression du contenu de la variable c */
putchar('c'); /* impression du caractère c */
Entrées/sorties avec spécification de formatex "entrées/sorties avec spécification de format"
Ces opérations sont réalisées par des fonctions de la bibliothèque standard du langage C.
Ecriture
printf("spécification_de_format", liste de variables);
Ainsi, l'impression des variables a et b, respectivement d'un format flottant, entier, suivie d'un caractère de passage à la ligne, s'écrit :
printf("%6.1f %d\n",a,b);
avec l'interprétation :
%6.1f 6 chiffres dont 1 après la virgule pour la variable a,
%d format entier pour la variable b,
\n caractère spécial indiquant le passage à la ligne suivante.
Exemple
int main(void)
{printf("salut,");printf("toi");printf("\n");}
// résultat du programme
salut,toi
Lecture
scanf("spécification_de_format", &variable_1,...,&variable_n);
Pour des raisons syntaxiques, l'opérateur & est obligatoire pour préfixer chaque nom de variable. Le reste de la syntaxe est similaire à celle de la fonction printf.
3 Structures de contrôle élémentaires
Nous présentons sommairement dans le présent paragraphe les structures de contrôle classiques de la programmation structurée que l'on retrouve en C.
Boucles
Boucleex "boucle" POUR
Sémantique EXEMPLE EN C
Pour i = 0 à n-1 faire for(i = 0; i=ex ">=" >= < décalage à droite
~ complément à un
Incrémentation
++ pré ou post incrémentation
-- pré ou post décrémentation
Priorité et règles d'associativité des opérateurs
Le tableau ci-dessous présente les règles d'associativité ou ordre d'évaluationex "ordre d'évaluation" des opérateurs (y compris ceux qui sont présentés plus loin). Les lignes sont présentées par ordre de priorité décroissante et les opérateurs de même priorité sont sur la même ligne.
Opérateurs Ordre d'évaluation
() [] ->. gauche à droite
! ~ ++ -- + - * & (type) sizeof droite à gauche
* / % gauche à droite
+ - gauche à droite
> gauche à droite
= > < gauche à droite
== != gauche à droite
& gauche à droite
^ gauche à droite
| gauche à droite
&& gauche à droite
|| gauche à droite
? : gauche à droite
= += -= etc. droite à gauche
, gauche à droite
On trouvera une application au § 5.3.3.
STRUCTURES DE CONTROLE
Nous présentons dans le présent chapitre la sémantique et la syntaxe des structures de contrôle du langage C qui sont les testsex "test" et les bouclesex "boucle".
Les tests
L'instruction if else
Synopsis
if (expression) instruction_v ou bloc_v
else instruction_f ou bloc_f
La séquence else est optionnelle.
Description : l'instruction if...elseex "if...else" évalue numériquement expression qui peut prendre la valeur numérique 0 (faux) ou 1 (vrai). L'instruction_v est exécutée si expression est non nulle, l'instruction_f sinon.
L'opérateur ternaire
La séquence :
if (condition) expression_v; else expression_f;
peut s'écrire avec l'opérateur ternaireex "opérateur ternaire" () ? : ex "() ? :" sous la forme :
(condition)? expression_v: expression_f
Exemple : ce programme imprime les composantes d'un tableauex "impression des composantes d'un tableau" d'entier par ligne de 10.
#include
int main(void)
{const n = 25;
int i, a[25];
for ( i = 0; i < n; i++ ) a[i] = i;
for ( i = 0; i < n; i++ )
printf( "%3d %c" , a[i], ( i % 10 == 9 || i==n-1) ? '\n' : ' ');
}
// résultat
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24
Description
Dix nombres sont imprimés par lignes.
La condition d'impression porte sur le caractère d'impression qui suit a[i].
Si le reste de la division de i modulo 10 est égal à 9 ou s'il est égal à N-1, c'est le caractère spécial \n (passage à la ligne). Sinon, c'est le caractère d'espacement. On obtient ainsi l'impression des coefficients du tableau a par groupe de dix en laissant un blanc après chaque colonne. Il faut noter que le test est possible dans une expression à l'intérieur des arguments de la fonction printf.
La construction else if
Synopsis
if (expression_1) instruction_1; ou bloc_1
else if (expression_2) instruction_2; ou bloc_2
else instruction_3; ...
Description
La construction else...ifex "else...if" évalue les expressions dans l'ordre de leur apparition. Si expression_ii est vraie, l'instruction associée est exécutée en séquence. Le dernier else traite le cas où aucune des conditions n'est satisfaite.
Principe de localisationex "principe de localisation"
Si il y a moins de else que de if, le dernier else est associé au dernier if sans else qui le précède, sauf dans le cas d'une structure de blocs englobant.
Exemple : les deux séquences suivantes ne sont pas équivalentes :
if ( n > 0 ) if (n>0)
if (a > b) {if (a>b) z = a;}
z = a;
else z = b; else z = b;
Description
si n>0 alors si n>0 alors
si a>b alors z = a si a>b alors z = a is
sinon z = b sinon z = b
is is
is
Les boucles
En C, les boucles sont toutes conditionnées par la comparaison du résultat de l'évaluation d'une expression à une valeur entière.
La boucle while
Synopsis : whileex "while"(expression_numérique) instruction ou bloc
Sémantique
Tant que (expression_numérique est non nulle) faire
instruction ou bloc
Finfaire
Exemple
Comptage des blancs, des tabulations et des caractères de fin de ligne d'un fichier.
#include
int main(void)
/* comptage de blancs (' '), tabulation (\t), */
/* passage à la ligne (\n) */
{int c, nb = 0 , ntab = 0, nl = 0;
while (( c = getchar()) != EOF )
{if (c == ' ' ) ++ nb;
else if (c == '\t') ++ntab;
else if (c == '\n') ++nl;
}
printf("nombre de blancs =");
printf("%d \n",nb);
printf("nombre de tabulations = %d",ntab);
printf("\nnombre de lignes = %d\n",nl);
}
// le fichier donnée est le fichier source
nombre de blancs = 71
nombre de tabulations = 7
nombre de lignes = 13
Exercice 3.1 : écrire un programme de comptage du nombre de lignes, de mots, de caractères du fichier d'entrée standard. Utiliser ce programme sur n'importe quel fichier texte.
Exercice 3.2 : écrire un programme de comptage des chiffres, lettres et autres caractères spéciaux (blanc, tabulation, fin de ligne). Les nombres des occurences des chiffres, des lettres de l'alphabet, des caractères spéciaux, seront respectivement stockées dans les variables chiffre, lettre, autre.
La boucle for
Synopsis : forex "for" (expression_1;expression_2;expression_3) instruction; ou bloc
expression_1 /* initialisation de la boucle */
expression_2 /* test de fin de boucle */
expression_3 /* expression de fin de boucle */
Description
Cette syntaxe très puissante peut se traduire sous la forme suivante :
expression_1 /* initialisation de la boucle */
tant que (expression_2)
instruction; ou bloc /* corps de la boucle */
expression_3; /* action exécutée à la fin de boucle */
Exemples
for (i=0; i=.
Initialisation
Pour initialiser un pointeur, il faut procèder comme suit :
int i= 5; /* définition et initialisation de i */
int *pi = &i; /*définition et initialisation d'un pointeurex "initialisation d'un pointeur" */
On peut aussi initialiser un pointeur à la valeur NULL en écrivant :
int *pi; pi = (int *) NULL;
Si on souhaite que pi pointe sur le caractère i, il faut utiliser l'opérateur de transtypage (cast) pour définir le type de l'objet pointé de la façon suivante :
int i = 5;
char *pi = (char*) &i;
L'initialisation d'un pointeur sur une fonction s'écrit :
int (*pfonc)(void) = fonc;
La variable pfonc est un pointeur sur l'adresse de la fonction entière sans argument fonc.
Conversion de pointeursex "conversion de pointeurs"
Un pointeur sur un objet d'un type donné peut être converti en un pointeur sur un objet d'un type différent. Le pointeur résultant peut être incohérent si le pointeur d'origine ne fait pas référence à un objet correctement aligné en mémoire.
La norme ANSI garantit qu'un pointeur sur un objet d'un type donné peut être converti en un pointeur sur un objet d'un type différent si ce dernier a un alignement strictement identique ou inférieur.
Le pointeur générique
Un pointeur génériqueex "pointeur générique" est un pointeur sur un objet dont le type, indéterminé à la compilation, est déterminé à l'exécution. Par exemple le résultat de la fonction malloc. Le type prédéfini associé au type void * est le type size_tex "size_t".
Il n'est pas possible d'accéder directement à l'objet pointé. Par contre, il est fondamental d'utiliser l'opérateur de transtypage pour convertir un pointeur de type void * en un pointeur sur un objet d'un type déterminé.
Le concept de pointeur générique permet de comparer des pointeurs sur des objets de type différent tout pointeur pouvant être comparé au pointeur générique. Elle permet également de simplifier l'écriture de certaines fonctions, par exemple la fonction fread, qui retourne un objet de type void *.
Chaînes de caractères et pointeurs
Une chaîne de caractères est un tableau de caractères, accessible par un pointeur sur son premier élément.
Toutes les fonctions de manipulation de chaînes de caractères peuvent être développées par utilisation exclusive des pointeurs.
Voici maintenant une application étonnante des pointeurs.
Règles d'initialisation
Considérons le programme suivant :
int main(void )
{char *chaine = "bonjour", *pchaine = chaine;
/* création d'une chaîne de caractères accessible par le pointeur pchaine*/
printf( " chaîne initiale : %s\n",chaine);
/* impression caractère par caractère */
while (*chaine!= '\0') {printf("%c",*chaine);chaine++;}
/* le pointeur a été modifié d'où aucune impression */
printf ("\n aucune impression %s\n",chaine);
/* réinitialisation du pointeur */
chaine= pchaine;
printf(" chaîne totale %s \n",chaine);
return(1);
}
// résultat
chaîne initiale : bonjour
bonjour
aucune impression
chaîne totale bonjour
Interprétation
La variable chaine est de type pointeur. Sa définition provoque la définition d'une chaîne de huit caractères que l'on peut imprimer (premier ordre d'impression). L'impression caractère par caractère prouve que la variable chaine est bien de type pointeur (deuxième impression). Le pointeur chaine ayant été incrémenté jusqu'à la fin de la chaîne, il n'y a plus rien à imprimer (troisième impression). Une réinitialisation du pointeur donne la dernière impression. On peut remarquer que l'on vient de créer une chaîne de caractères sans nom explicite. En fait, l'instruction
char *pchaine = "bonjour";
définit un pointeur sur une chaîne de caractères et provoque la réservation de place en mémoire pour la chaîne de caractères elle-même.
Le programme ci-dessous confirme que la variable chaine est un pointeur.
#include
int main(void )
{char *chaine ="bonjour";
printf("chaine = %s sizeof(chaine) = % d\n", chaine, sizeof(chaine));
printf("&chaine = %x *chaine = %c \n",&chaine, *chaine);
printf("sizeof(*chaine) = % d\n", sizeof(*chaine));
return(1);
}
// résultats
chaine = bonjour sizeof(chaine) = 2
&chaine = ffde *chaine = b
sizeof(*chaine) = 1
Ce programme montre d'abord que la variable chaine est un pointeur sur un objet de type char puisque sa taille est de deux octets. D'autre part, ce pointeur de deux octets pointe la chaîne de caractères "bonjour".
Il y a une différence entre un tableau de caractères et un pointeur sur une chaîne de caractères.
Soient les deux instructions :
char message[] = "Bonjour Monsieur ";
char *pmessage = "Bonjour Monsieur ";
message est un tableau dont la taille est fixée à la définition,
pmessage est un pointeur initialisé sur le premier caractère de la chaîne.
Exemples d'initialisation
Voici quatre méthodes d'initialisation d'une chaîne de caractères.
Exemple 1 : initialisation caractère par caractère
#include
int main(void )
{char chaine[30] = "bonjour"; int i;
char * pchaine = &chaine;
for(i=0; chaine[i]!='\0';i++) printf("chaine[i] = %c\n",chaine[i]);
while (*pchaine != '\0') {printf(" %c \n",*pchaine);*pchaine++;}
printf("sizeof(chaine) = % d\n",sizeof(chaine));
printf("&chaine = %x , pchaine = %x\n",&chaine, pchaine);
printf("&chaine[0] = %x\n",&chaine[0]);
printf("chaine = %s\n",chaine);
pchaine = &chaine;
*pchaine = 'c';
printf("%s\n",chaine);
return(1);
}
// résultats
chaine[i] = b /* écriture caractère par caractère */
chaine[i] = o
chaine[i] = n
chaine[i] = j
chaine[i] = o
chaine[i] = u
chaine[i] = r
b
o
n
j
o
u
r
sizeof(chaine) = 30
&chaine = ffd8 , pchaine = ffdf
&chaine[0] = ffd8
chaine = bonjour
conjour
Exemple 2
#include
#define TAILLE 30
int main(void )
{int i = 0;
char chaine[TAILLE],*pchaine = "bonjour";
while(*pchaine != '\0')
chaine[i++]=*pchaine++;chaine[i++]='\0';
printf("\n %d",i);
printf("\n chaîne initiale %s ",chaine);
return(1);
}
// résultat
8
chaîne initiale bonjour
Exemple 3
#include
#define TAILLE 30
int main(void )
{int i = 0;
char chaine[TAILLE],*pchaine = "bonjour";
while (*pchaine!='\0') chaine[i++] = *pchaine++;
chaine[i]='\0';
printf("\n %d",i);
printf("\n chaîne initiale %s ",chaine);
return(1);
}
// résultat
7
chaîne initiale bonjour
Exemple 4
#include
#define TAILLE 30
int main(void )
{int i = 0;
char chaine[TAILLE],*pchaine = "bonjour";
for (i=0;*pchaine != '\0';chaine[i++] = *pchaine++);
chaine[i] = '\0';
printf("\n %d",i);
printf("\n chaine initiale %s ",chaine);
return(1);
}
// résultat
7
chaine initiale bonjour
Taille
Le calcul de la taille d'une chaîne de caractère permet de mettre en évidence la puissance et la concision résultantes de l'utilisation des pointeurs.
Voici quatre versions, de plus en plus compactes, d'une fonction de calcul de la longueur d'une chaîne de caractèresex "calcul de la longueur d'une chaîne de caractères".
Version 1
#include
int main(void )
/* fonction str() première version : boucle for */
{char chaine[] ="bonjour";
int str(char *); /* prototype */
printf("\n la chaîne %s a %d caractères\n", chaine,str(chaine));
return(1);
}
int str(char *s) /* calcul de la longueur de la chaine */
{int n;
for (n = 0; *s != '\0'; s++) n++;
return(n);
}
// résultat
la chaine bonjour a 7 caractères
Version 2
#include
int main(void ) /* str : deuxième version (boucle while) */
{char *chaine ="bonjour";
char *str(char *); /* prototype */
printf("\n la chaine %s a %d caractères\n", chaine,str(chaine));
return(1);
}
char *str(char *s) /* calcul de la longueur de la chaine */
{char *n = s; /* initialisation de n, pas de *n */
while (*n != '\0') n++;
return(n-s); /* soustraction de pointeurs */
}
// résultat
la chaine bonjour a 7 caractères
Version 3 : seule, la fonction change.
/* str : troisième version boucle while simplifiée */
char *str(char *s)
{char *n = s; /* initialisation de n, pas de *n */
while (*n) n++;
return(n-s);
}
// résultat
la chaîne bonjour a 7 caractères
Version 4
/* str : quatrième version boucle while simplifiée */
char *str(char *s)
{char *n = s;
while (*n++) ;
return(n-1-s);
}
// résultat
la chaîne bonjour a 7 caractères
Recopie
Exercice 5.9.: écrire trois versions différentes d'un programme de recopie de chaine. Le premier programme utilise des tableaux. Les deux autres utilisent des pointeurs.
Comparaison
La fonction strcmpex "strcmp" compare, caractère par caractère et dans l'ordre lexicographique, le nombre et la valeur des occurrences de deux chaînes de caractères. Suivant la valeur de l'argument de retour, la longueur de s est :
inférieure à celle de t s'il est négatif,
égale à celle de t s'il est nul,
supérieure à celle de t s'il est positif.
On pourra écrire une version avec tableau et une version avec pointeur.
Version 1 : utilisation de tableau
Fonction strcmp(char s, char t) : comparaison de chaînes de caractèresex "comparaison de chaînes de caractères"
{int i; Algorithme
i = 0; i : = 0
while (s[i] == t[i]) tant que s[i] = t[i] faire
if (s[i++] == '\0') si s[i] = '\0' return (0) is
return(0); i : = i+1
return(s[i] - t[i]);
} fin_faire
Version 2 : utilisation de pointeurs
char *strcmp (char *s, char *t)
{for (; *s = *t; *s++, *t++)
if (*s == '\0') return (0);
return(*s - *t);
}
Concaténation
Exercice 5.10 : écrire deux programmes de concaténation de chaînes de caractères : une version tableau et une version pointeur.
Quelques applications des pointeurs
Transmission d'arguments à la fonction main
Une application fondamentale de la notion de tableaux de dimension incomplète est d'écrire des fonctions dont le nombre d'arguments d'appel peut être variable. Cette application est utilisée pour transmettre, depuis l'interprète de commandes, un nombre quelconque d'arguments à une commande sous forme de chaînes de caractères.
Le principe de base est le suivant : deux arguments sont transmis lors de l'appel de la fonction main. Ce sont :
int argcex "argc"; /* nombre d'arguments de l'appel */
char *argvex "argv"[]; /* liste des arguments_d'appel */
argv est un tableau de pointeurs sur des chaînes de caractères.
Exemple : soit l'exécution de la commande echo sous UNIX avec les arguments echo bonjour monsieur
On obtient :
argc = 3
argv[0] = echo\0; **argv = *argv[0] = e
argv[1] = bonjour\0; *++argv[1]= *(++argv[1]) = o
argv[2] = monsieur\0
Version 1 /* fichier echo1.c */
int main(int argc, char *argv [])
/* argument argc : nombre d'arguments */
/* argv : tableau de pointeurs sur les arguments */
/* transmis lors de l'appel de main */
{int i;
for (i = 0; i < argc; i++)
printf("%s%c",argv[i], (i ",i);
/* test sur le type de l'argument suivant et mise à jour de la pile */
switch(va_arg(p_liste,int))
{case 0 : /* type char * */
/* mise à jour du pointeur p_liste */
printf("type : char * valeur : %s\n", va_arg(p_liste, char *)); break;
case 1 : /* type int */
printf("type : int valeur : %d\n",va_arg(p_liste, int)); break;
case 2 : /* type double */
printf("type : double valeur : %lf\n",va_arg(p_liste, double)); break;
case 3 : /* type float : attention à la conversion de type */
printf("type : float valeur : %lf\n",va_arg(p_liste, double)); break;
/* l'instruction : */
/* printf("type : float valeur : %lf\n",va_arg(p_liste, float)); */
/* ne fonctionne pas */
}
}
va_end(p_liste);
/* recommandé pour ultime mise à jour du pointeur de pile*/
}
int main(void )
{printf("\t\nPremier appel\n"); /* 3 arguments : chaîne, entier, double */
f_var_arg(3,0,"abcdefg",1,255,2,3.5657);
printf("\n\tDeuxième appel\n"); /* 4 arguments : entier, chaîne, entier, double */
f_var_arg(4,1,-2345,0,"DFGHbhj",1,456,2,-67.67);
printf("\n\tTroisième appel\n");
/* 5 arguments : double, chaîne, entier, flottant (double), chaîne */
f_var_arg(5,2,-23.45,0,"Bonjour",1,-56,3,-7.67,0,"Au revoir");
return(1);
}
// résultat
Premier appel
paramètre 0 --> type : char * valeur : abcdefg
paramètre 1 --> type : int valeur : 255
paramètre 2 --> type : double valeur : 3.565700
Deuxième appel
paramètre 0 --> type : int valeur : -2345
paramètre 1 --> type : char * valeur : DFGHbhj
paramètre 2 --> type : int valeur : 456
paramètre 3 --> type : double valeur : -67.670000
Troisième appel
paramètre 0 --> type : double valeur : -23.450000
paramètre 1 --> type : char * valeur : Bonjour
paramètre 2 --> type : int valeur : -56
paramètre 3 --> type : float valeur : -7.670000
paramètre 4 --> type : char * valeur : Au revoir
STRUCTURES DE DONNEES
Un enregistrement logiqueex "enregistrement logique" permet de représenter un objet abstrait d'une l'application et d'en structurer les données. Par exemple, un agenda est constitué d'informations formées du nom, du prénom, de l'adresse, du numéro de téléphone de différentes personnes.
Il est ensuite nécessaire de définir des opérations à réaliser sur ces objets à savoir description et utilisation.
En programmation objet, l'objet abstrait est une classe, et les traitements associés sont appelées méthodes.
Structures
Le langage C permet de définir des structures de donnéesex "structure de données" abstraites à partir des structures et des unions.
Définition
Une structureex "structure" de données est un agrégatex "agrégat" d'une ou plusieurs variables, de type éventuellement différent. La déclaration structex "struct" permet à l'utilisateur de définir un tel objet. On peut ainsi définir des enregistrements logiquesex "enregistrement logique" constitués de plusieurs champex "champ"s ou zoneex "zone"s. Les champs d'une structure sont stockées séquentiellement. La taille nécessaire au stockage d'une structure est le total des tailles des différents champs qui la constituent, complété si nécessaire d'octets d'alignement.
Remarque
En programmation objet, les champs sont appelés données membres ou attributs.
Déclaration et définition
Déclaration
La déclaration structex "déclaration struct" permet de décrire les champs constituant la structure. Elle définit la structure de données des variables qui lui seront associées et ne provoque aucune allocation mémoire.
Définition
La déclaration struct devient une définitionex "définition" quand une variable lui est associée.
INCORPORER Designer \s \* fusionformat
Diagramme d'utilisation du mot clé struct
Interprétation
struct mot clé
nom_structure déclaration (optionnelle) du nom abstrait du type de la structure (tag), permettant de déclarer un nouveau type de variable
{....} marque de début/fin de la description de la structure
type type prédéfini ou non du champ
déclarateur identificateur du champ
identificateur définition optionnelle des identificateurs des variables associées à la structure décrite
Exemples
Déclaration de la structure abstraite date
struct date {int jour; int mois; int année;};
Définition des variables de type date aujourd_hui, demain
struct date aujourd_hui,demain;
Autre possibilité avec un résultat identique
struct date {int jour; int mois; int année;} aujourd_hui,demain;
On aurait pu aussi écrire :
struct {int jour; int mois; int année;} aujourd_hui, demain;
Dans ce cas, le type de structure abstraitex "type de structure abstrait" date n'aurait pas été déclaré.
Règles d'utilisations
Une structure peut être définie à partir d'une autre structure. Ainsi, la déclaration
#define TAILLE 50
struct pers{char nom[TAILLE]; char prenom[TAILLE]; struct date date_mois;} individu;
est licite. C'est la définition d'une structure pers et la déclaration d'un identificateur individu de type pers. Le symbole date_mois est de type prédéfini date.
Initialisation d'une structureex "initialisation d'une structure"
Une structure externe ou statique peut être initialisée de la façon suivante :
struct pers individu = {"Dupont", "Albert", 20, 3, 53};
Récursivitéex "récursivité"
Une définition récursive est autorisée le déclarateur pouvant être la structure elle-même.
Exemple : soit la structure noeud :
struct noeud {int code; struct noeud *gauche; struct noeud *droite;};
Elle a la représentation suivante en mémoire :
INCORPORER Designer \s \* fusionformat
Exemples
struct noeud arbre[10 ]; /* déclaration d'un tableau de 10 noeuds */
struct noeud *p; /* p est un pointeur sur une structure noeud */
struct noeud *fils_gauche(void);
/* fils_gauche est une fonction sans argument retournant un pointeur vers une structure de type noeud */
Accès aux champs d'une structure : l'opérateur de sélection de membre
L'opérateur de sélection de membre.ex "opérateur ." permet d'accéder à une variable d'un champ d'une structure. Il assure le lien entre le nom de la structure et le nom de la variable associée par une construction de la forme :
nom_variable.nom_du_champ[.nom_du_champ]
Une telle construction est arborescente.
Exemple
demain.jour = 20;
individu.date_mois.jour = 20;
Pointeurs sur des structures : l'opérateur ->
La définition :
struct date *pt;
définit la variable pt de type pointeur sur la structure date.
Pour accéder au champ mois, on écrit :
(*pt).mois = 11 ; (1)
Les parenthèses sont ici nécessaires compte tenu de la hiérarchie des opérateurs * et ().
L'opérateur binaire ->ex "opérateur ->" permet l'accès direct à une zone de la structure. Il est défini de la façon suivante :
L'intruction
p -> x;
est équivalente à l'instruction :
(*p).x;
Ainsi, l'instruction (1) s'écrit également :
pt ->mois = 11;
Exemple
#include
#define TAILLE 30
int main(void)
{/* déclaration des types structure date et structure personne */
struct date {int jour; int mois; int annee;};
struct personne {char nom[TAILLE]; char prenom[TAILLE]; struct date date_mois;};
/* définition des variables demain et individu */
struct date demain;
/* initialisation de la variable individu de type personne */
struct personne individu = {"Dupond","Jean - Albert",20,3,53 };
printf("Caractéristiques initiales de l'individu :\n");
printf("Nom : %s\t Prénom : %s \n",individu.nom, individu.prenom);
printf("Date de naissance : %d - %d - %d \n",
individu.date_mois.jour,
individu.date_mois.mois,
individu.date_mois.annee);
printf("fin du premier acte\n ");
/* autres formes d'initialisation */
demain.jour = 20;
printf("La date de demain est : %d\n ",demain.jour);
individu.nom = "Dupont";
individu.prenom = "Albert ";
individu.date_mois.jour = 20;
individu.date_mois.mois = 03;
individu.date_mois.annee = 1920;
/* impression des résultats*/
printf("Caractéristique de individu :\n");
printf("Nom : %s\t Prénom : %s \n",individu.nom, individu.prenom);
printf("Date de naissance : %d - %d - %d \n",
individu.date_mois.jour,
individu.date_mois.mois,
individu.date_mois.annee);
}
// résultat
Caractéristiques initiales de l'individu :
Nom : Dupond Prénom : Jean - Albert
Date de naissance : 20 - 3 - 53
fin du premier acte
La date de demain est : 20
Caractéristique de individu :
Nom : Dupont Prénom : Albert
Date de naissance : 20 - 3 - 1920
Associativité des opérateurs * ->++ -- () [ ]
Nous avons vu précédemment que la hiérarchie des opérateurs de manipulation des pointeurs est la suivante :
Opérateurs Ordre d'évaluation
() [ ] ->. gauche à droite
! ~ ++ -- - * & sizeof droite à gauche
La bonne utilisation de ces règles nécessite certaines précautions.
Exemple
#include
int main(void)
{int i = 16, j = 32;
int x = 0;
int *pt =&i;
printf(" valeur d'initialisation\n");
printf(" i = %d j = %d",i,j);
printf(" &i = %x &j = %x\n", &i,&j);
printf(" \n\névaluation de pt\n");
printf(" i = %d *pt = %d pt = %x\n", i, *pt, pt);
*pt++;
/* cette expression est équivalente à *(pt++)
qui provoque un avertissement: cette expression doit être une Lvalue.
*/
printf("\n\n évaluation de *pt++\n");
printf(" *pt = %d pt = %x\n", *pt, pt);
(*pt)++;
/* pt est inchangé.
Cette expression n'a de sens que pour une Lvalue
*/
printf("\n\n évaluation de (*pt)++\n");
printf(" *pt = %d pt = %x\n", *pt, pt);
x=*(pt++); /* x = *pt; pt++ ; */
printf("\n\n évaluation de x = *(pt++)\n");
printf(" x =%d *pt = %d pt = %x\n", x, *pt, pt);
x=*pt++; /* x = *pt; pt++ */
printf("\n\n évaluation de x = *pt++\n");
printf(" x =%d *pt = %d pt = %x\n", x, *pt, pt);
return(1);
}
Exercice 6.1 : poursuivre les tests de l'exemple précédent.
Applications
1°) Vu les règles d'associativité, l'instruction :
++p -> x est équivalente à l'instruction ++(*p).x
D'où incrémentation du champ x, pas du pointeur p.
Démonstration :
++p ->x = ++(p->x) = ++((*p).x)) = ++(*p).x
La première égalité résulte de la hiérarchie des opérateurs -> et ++.
La deuxième est une conséquence de la définition de l'opérateur ->.
La troisième résulte de la hiérarchie des opérateurs (),.,++.
2°) L'instruction
++p -> x;
n'est pas équivalente à l'instruction (L-valeurex "L-valeur" ou R-valeurex "R-valeur")
(++p) -> x;
En effet, la deuxième instruction s'écrit :
(++p) -> x = (*(++p)).x
L'instruction exécute la postincrémentation du pointeur après un accès au champ x, l'expression entre parenthèses étant évaluée en premier.
3°) Soit l'instruction :
(p++) ->x =...;
Compte tenu des règles de priorité, on a :
(p++) ->x =...CARSPECIAUX 219 \f "Symbol" (*(p++)).x =... ;
L'opérateur de postincrémentation ++ étant utilisé, l'instruction précédente s'écrit :
((*p)).x =...; p++;
4°) L'instruction (*p)-> y accède à l'objet pointé par y puisque :
(*p)-> y = (*(*p)).y = (**p).y
5°) L'instruction (*p)->y++ est une R-valeur ou une L-valeur. Son effet est de postincrémenter le champ y, puisque :
(*p)-> y++ = ((**p).y)++
6°) Compte tenu des hiérarchies respectives des opérateurs ++ et ->, la postincrémentation du pointeur pt s'écrit pt++->y.
7°) La postincrémentation du pointeur *p s'écrit (*p)++ -> y. Il suffit de poser q = *p dans l'égalité précédente.
8°) L'instruction (*p++)->y postincrémente le pointeur p puisque :
(*p++)->y = (*(p++))->y
Exercice 6.2
Ecrire un programme qui teste la hiérarchie et l'associativité des opérateurs (),*,->,. utilisés avec l'opérateur d'affectation, sur des expressions situées à gauche de cet opérateur (Lvaleur) ou à droite (Rvaleur).
Pointeurs, structures et fonctions
Les opérations sur les structures sont les suivantes :
accès à son adresse avec l'opérateur &,
accès, puis modification d'un champ avec l'opérateur ->,
transmission d'une structure comme argument d'une fonction par valeur ou par adresse (norme ANSI),
retour d'une structure par une fonction.
Exemple 1 : ce programme définit les types structurés date et personne. La fonction lit effectue la saisie des champs d'un individu de type personne. Les arguments sont transmis par adresse. Les résultats sont écrits de deux façons différentes par appel des fonctions ecrit et ecrit2. La première transmet la structure par valeur, ce qui est naturel puisqu'aucune modification ne doit être faite à l'écriture, la seconde par adresse.
#include
#define TAILLE 30
/* déclaration des variables globales */
struct date {int jour; int mois; int annee;};
struct personne { char nom[TAILLE]; char prenom[TAILLE]; struct date date_mois;};
void main(void)
{/* définitions */
struct date demain;
struct personne individu;
/* prototypes */
void ecrit(struct personne); /* transmission par valeur (la meilleure) */
void ecrit2(struct personne *); /* transmission par adresse */
void lit (struct personne *); /* transmission par adresse */
/* initialisation */
demain.jour = 20;
printf(" La date de demain est : %d\n ", demain.jour);
/* lecture d'une structure */
lit(&individu); /* transmission par adresse */
/* écriture d'une structure */
ecrit(individu); /* transmission par valeur */
ecrit2(&individu); /* transmission par adresse */
}
void lit (struct personne * pindividu)
{
(*pindividu).nom = "Dupont"; /* remplissage du champ nom */
pindividu->prenom = "Albert "; /* remplissage du champ prénom */
/* remplissage du champ date */
pindividu->date_mois.jour = 20;
(*pindividu).date_mois.mois = 03;
pindividu->date_mois.annee = 1920;
}
void ecrit( struct personne individu)
/* transmission par valeur */
{printf(" Caractéristique de individu :\n");
printf(" Nom : %s \t Prénom : %s \n", individu.nom, individu.prenom);
printf("Date de naissance : %d - %d - %d \n",
individu.date_mois.jour,
individu.date_mois.mois,
individu.date_mois.annee);
}
void ecrit2( struct personne *individu) /* transmission par adresse */
{printf(" Caractéristique de individu :\n");
printf(" Nom : %s \t Prénom : %s \n", individu->nom, individu->prenom);
printf(" Date de naissance : %d - %d - %d \n",
individu->date_mois.jour,
individu->date_mois.mois,
individu->date_mois.annee);
}
// résultat
La date de demain est : 20
Caractéristique de individu :
Nom : Dupont Prénom : Albert
Date de naissance : 20 - 3 - 1920
Caractéristique de individu :
Nom : Dupont Prénom : Albert
Date de naissance : 20 - 3 - 1920
Exemple 2 : extrait du fichier stdio.h : définition de la structure _iobuf
#define _NFILE 20 /* nombre maximum de fichiers */
struct _iobuf
{char *_ptr; /* pointeur sur le buffer courant */
int _rcnt; /* compteur d'octets en lecture */
int _wcnt; /* compteur d'octets en écriture */
char *_base; /* adresse de base du tampon d'entrée/sortie */
char _flag; /* drapeau de contrôle */
char _file; /* numéro de fichier */
int _size; /* taille du tamon */
char _cbuff; /* tampon de caractère */
}
extern struct _iobuf _iob[_NFILE];
Dans l'exercice qui suit, on définit les opérations d'addition et soustration de nombres complexes. En C++, il est possible de surcharger les opérateursEX "surcharge d'opérateurs" + et * de telles sorte que l'appel de ces fonctions d'addition et de multiplication s'écrivent avec ces opérateurs usuels.
Exercice 6.3 : écrire un programme de :
saisie d'un nombre complexe,
calcul de la somme et du produit de nombres complexes,
affichage des résultats.
Tableau de structures
Il est possible de définir des tableaux de structuresex "tableau de structures". Ainsi, l'instruction
struct t tabs [8 ] [3 ];
définit huit tableaux de trois structures de type t.
Exemple
#include
#define NLIGNE 2
#define NCOL 3
#define NTAB 4
struct Super_tableau {int tab[NLIGNE][NCOL][NTAB]; };
int main (void)
{ /* tableaux et structures */
struct Super_tableau super_tableau, *pt = &super_tableau, super_tableau2;
void remplissage(struct Super_tableau *);
void affichage(struct Super_tableau);
remplissage(pt);
printf("\nAffichage de super_tableau\n");
affichage(super_tableau);
super_tableau2 = super_tableau;
printf("\nAffichage de super_tableau2\n");
affichage(super_tableau2);
return(0);
}
void remplissage(struct Super_tableau * tableau)
{int i , j, k ;
for (i = 0 ; i < NLIGNE ; i++)
for (j = 0 ; j < NCOL; j++)
for (k =0 ; k < NTAB; k++) tableau->tab[i][j][k] = i+j+k;
}
void affichage (struct Super_tableau tableau)
{int i , j, k , compteur = 0;
for (i = 0 ; i < NLIGNE ; i++)
for (j = 0 ; j < NCOL; j++)
for (k =0 ; k < NTAB; k++)
{
if(compteur %4 !=0)
printf(" tab[%d][%d][%d] = %d ",i,j,k,tableau.tab[i][j][k]);
else printf("\n tab[%d][%d][%d] = %d ", i, j, k, tableau.tab[i][j][k]);
compteur++;
}
printf("\n");
}
// résultat
Affichage de super_tableau
tab[0][0][0] = 0 tab[0][0][1] = 1 tab[0][0][2] = 2 tab[0][0][3] = 3
tab[0][1][0] = 1 tab[0][1][1] = 2 tab[0][1][2] = 3 tab[0][1][3] = 4
tab[0][2][0] = 2 tab[0][2][1] = 3 tab[0][2][2] = 4 tab[0][2][3] = 5
tab[1][0][0] = 1 tab[1][0][1] = 2 tab[1][0][2] = 3 tab[1][0][3] = 4
tab[1][1][0] = 2 tab[1][1][1] = 3 tab[1][1][2] = 4 tab[1][1][3] = 5
tab[1][2][0] = 3 tab[1][2][1] = 4 tab[1][2][2] = 5 tab[1][2][3] = 6
Affichage de super_tableau2
tab[0][0][0] = 0 tab[0][0][1] = 1 tab[0][0][2] = 2 tab[0][0][3] = 3
tab[0][1][0] = 1 tab[0][1][1] = 2 tab[0][1][2] = 3 tab[0][1][3] = 4
tab[0][2][0] = 2 tab[0][2][1] = 3 tab[0][2][2] = 4 tab[0][2][3] = 5
tab[1][0][0] = 1 tab[1][0][1] = 2 tab[1][0][2] = 3 tab[1][0][3] = 4
tab[1][1][0] = 2 tab[1][1][1] = 3 tab[1][1][2] = 4 tab[1][1][3] = 5
tab[1][2][0] = 3 tab[1][2][1] = 4 tab[1][2][2] = 5 tab[1][2][3] = 6
Assignation de structure
Une structure peut directement être assignée à une autre structure à condition qu'elles soient constituées des mêmes champs.
Exemple
#include
void main(void)
{ typedef struct{ int jour; int mois; int annee;} date;
date jour1 = {20,12,1988}, jour2 = jour1;
printf("jour = %d mois = %d annee = %d\n", jour1.jour, jour1.mois, jour1.annee);
printf("jour = %d mois = %d annee = %d\n", jour2.jour, jour2.mois, jour2.annee);
}
// résultat
jour = 20 mois = 12 annee = 1988
jour = 20 mois = 12 annee = 1988
Application : assignation globale d'un tableau
Il suffit de stocker un tableau dans une structure pour pouvoir l'assigner globalement. C'est d'ailleurs le seul moyen en C.
int main(void)
{struct GrossTab {int tableau[5];} tab1, tab2 = {1, 2, 3, 4, 5};
int i;
printf("sizeof (tab1) = %d",sizeof(tab1));
for (i=0; i sexe = 1; break;
default : printf("erreur sexe\n") ; return(0); }
printf("année de naissance (2 chiffres) ");
scanf("%u",&annee);
if(annee > 99) {printf("erreur année\n") ; return(1);}
secu->annee=annee;
printf("\nmois (1-12) ");
scanf("%u", &mois);
if(mois > 12) { printf("erreur mois \n"); return(2);}
secu->mois=mois;
printf(" \nnuméro de département (1-98) ");
scanf("%u",&departement);
if(departement > 98) {printf("erreur departement\n"); return(3);}
secu->departement=departement;
printf(" \nnuméro de canton (0-999) ");
scanf("%u",&canton);
if(canton > 999) {printf("erreur canton\n"); return(4);}
secu->canton=canton;
printf(" \nnuméro de quantième (0-999) ");
scanf("%u",&quantieme);
if(quantieme > 999) {printf("erreur quantième\n"); return(5);}
secu->quantieme=quantieme;
}
void ecriture(struct SecSoc secu)
{ char * Mois[12] = {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet",
"Août" ,"Septembre", "Octobre", "Novembre" , "Décembre" } ;
printf("\nsexe (0-masculin, 1-féminin) : %u\n",secu.sexe);
printf("année de naissance 19%u\n", secu.annee);
printf("mois de naissance : %s \n",Mois[secu.mois-1] );
printf("département de naissance : %u \n",secu.departement);
printf("canton : %u \n",secu.canton);
printf("quantième : %u \n",secu.quantieme);
}
// résultat
sexe : 1 (garçon) ou 2 (fille) 1
année de naissance (2 chiffres) 53
mois (1-12) 03
numéro de département (1-98) 75
numéro de canton (0-999) 012
numéro de quantième (0-999) 130
sexe (0-masculin, 1-féminin) : 0
année de naissance 1953
mois de naissance : Mars
département de naissance : 75
canton : 12
quantième : 130
306a e97 316 2090
306a
sizeof(unsigned int) = 2
sizeof(struct (SecSoc)) = 8
Unions
Les structures et les unions ont une syntaxe d'utilisation très voisine. Par contre, la sémantique est très différentes : les structures sont constitués de champs consécutifs, les unions sont constitués de champs qui peuvent se recouvrir.
Déclaration et définition
La déclaration unionex "union" permet de définir des structures de données permettant de manipuler alternativement des objets de différents types localisés à une même adresse dans la mémoire. Une union s'apparente à une structure dont toutes les zones sont superposées sur la même adresse. Une variable peut ainsi être interprétée de plusieurs façons différentes.
La syntaxe de l'instruction union est similaire à celle de l'instruction struct.
Exemple
union typunion {char car;int entier;long longueur;double x;}var;
La variable var est de type typunion.
Le programmeur peut sélectionner le type de la variable de son choix. Ainsi peut-on écrire :
int i;
double y;
typeunion var1, var2;
var1.entier = i;
var2.x = y;
Exemple 1
int main(void)
{union bizarre{ char car;int entier;long longueur;double x; }
var1, var2;
int i = 5;
double y = 38.6785;
var1.entier = i;
var2.x = y;
var1.car = 'c';
printf(" var1.car = %c var1.entier = %d\n
var1.longueur = %d var1.x = %e\n",
var1.car, var1.entier, var1.longueur,
var1.x);
printf(" var2.car = %c var2.entier = %d\n
var2.longueur = %d var2.x = %e\n",
var2.car, var2.entier,
var2.longueur, var2.x);
}
// résultats
var1.car = c var1.entier = 99
var1.longueur = 99 var1.x = 2.199951e-034
var2.car = _ var2.entier = 11011
var2.longueur = 11011 var2.x = 2.356809e+110
Le seul résultat exact est la valeur de var1.car. Tous les autres résultats sont fantaisistes car les variables imprimées n'ont pas été initialisées.
Exemple 2
void main(void)
{ union bizarre /* déclaration de bizarre */
{ char car;int entier;long longueur;double x; };
/* définition des variables var1 et var2 */
union bizarre var1, var2;
int i = 5;
double y = 38.6785; printf("y = %e\n",y);
var1.entier = i;
printf("var1.entier = %d\n",var1.entier);
var2.x = y;
printf("var2.x = %e\n",var2.x);
var1.car = 'c'; /* modifie l'interprétation de var1.entier */
printf(" var1.car = %c var1.entier = %d
var1.longueur = %d var1.x = %e \n",
var1.car, var1.entier,var1.longueur,var1.x);
printf(" var2.car = %c var2.entier = %d
var2.longueur = %d var2.x = %e\n",
var2.car, var2.entier,var2.longueur,var2.x);
}
// résultats
y = 3.867850e+001
var1.entier = 5
var2.x = 3.867850e+001
var1.car = c var1.entier = 99 var1.longueur = 99
var1.x = 1.390369e+093
var2.car = _ var2.entier = 11011 var2.longueur = 11011
var2.x = 2.356809e+110
Structure et union
Une structure de type union peut être imbriquée dans une structure de type struct.
Type énuméré
Le mot clé enumex "enum" permet de définir un ensemble ordonné fini de constantes symboliques de type entier.
Forme
enum nom_de_type {liste de constantes symboliques} [liste_de_variables] ;
Exemple
enum jours {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche};
jours jour; /* la variable jour est de type jours */
Les instances de la liste d'énumérationex "liste d'énumération" sont considérées comme des constantes entières dont les valeurs sont numérotées à partir de zéro par défaut, avec un pas de 1. Ainsi nous obtenons :
lundi = 0; mardi = 1;...
et l'instruction
jour = mardi;
est équivalente à l'instruction
jour = 1;
L'initialisation explicite du premier élément de la liste avec une valeur entière différente de zéro est autorisée. Par exemple :
enum mois { Janvier = 1, Février, Mars,...., Décembre };
Exemple
int main(void)
{enum jours {lundi, mardi, mercredi, jeudi,vendredi, samedi, dimanche};
enum mois {Janvier = 1, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre,
Octobre, Novembre, Décembre} month;
enum jours jour;
jour = lundi;
printf ("jour = %d\n",jour);
jour = dimanche;
printf ("jour = %d\n",jour);
month = Décembre;
printf("month = %d\n",month);
}
// résultats
jour = 0
jour = 6
month = 12
L'instruction typedef
Type synonyme
L'instruction typedefex "typedef" permet de définir un nouveau nom de type ou type synonymeex "type synonyme".
Un nouvel identificateur de type peut être défini sans création d'un type nouveau et être utilisé pour tous types d'objets structurés tels les tableaux, les structures, les unions et les énumérations, ce qui permet d'améliorer la lisibilité du programme.
Syntaxe : typedef nom_de_type_synonyme;
Exemple
#include
int main(void)
{typedef int entier; /* entier synonyme de int */
typedef char octet; /* octet synonyme de char */
typedef char *texte [];
typedef struct {float reel; float imaginaire;} complexe;
entier i = 1;
texte chaine = "bonjour";
octet byte = '\063';
complexe z;
z.reel = 1.0;
printf("i =%d chaine = %s byte = %c reel %e\n", i, chaine, byte, z.reel );
}
// résultat
i = 1 chaine = bonjour byte = 3 reel 1.000000E+00
Il est fortement recommandé d'utiliser l'instruction typedef pour résoudre les problèmes liés à la portabilité. Les tailles des objets de base (int, short, long...) étant souvent différentes d'une machine à une autre machine.
Il est possible de définir un synonyme pour des types différents.
Exemple
typedef int short char long geant;
Le type geant est synonyme pour tous les types précédents.
Définition d'un nom de type
L'utilisation d'un type défini à partir d'une structure nécessite la répétition du mot clé struct ce qui peut être lourd. Pour l'éviter, il faut utiliser l'instruction typedef de la façon suivante :
typedef struct [nom_de_la_structure] {type1 champ1;..., typep champ;] nom_de_la_structure;
Exemple
#define TAILLE 5
int main(void)
{typedef struct {char nom[TAILLE]; char prenom[TAILLE];} chaine;
typedef struct chaine2 {char nom[TAILLE]; char prenom[TAILLE];} chaine2;
chaine personne;
chaine2 personne2;
}
Rappels sur les structures de données classiques
Tableau - pointeur - liste - table file
Tableau, pointeur
L'organisation la plus simple des informations en mémoire est séquentielle. Cette structure, appelée tableau, est bien adaptée au calcul scientifique. L'accès à l'information est réalisé par un pointeurex "pointeur" (index) qui prend successivement les adresses des informations (typées) rangées en séquence.
INCORPORER Designer \s \* fusionformat
Tableau
Liste
Une listeex "liste" chainée est un ensemble d'informations chacune composés de deux parties: l'information proprement dite, complétée par un pointeur contenant l'adresse de l'information suivante de la liste :
INCORPORER Designer \s \* fusionformatListe chaînée
Les opérations sur une liste chaînée sont l'initialisation d'un élément de la liste, l'insertion ou la suppression d'un des éléments de la liste, le parcourt de la liste..
Exemple
Les blocs d'un fichier ne sont pas toujours consécutifs sur le disque; chacun contient, en dernière information, l'adresse sur le disque du bloc qui lui est (logiquement) consécutif constituant ainsi une liste chaînéeex "liste chaînée" par des pointeurs.
Table
Une structure très utilisée en informatique de gestion est tableex "table" : l'accès aux informations d'un fichier est réalisé par l'intermédiaire d'une clé d'accès contenue dans un autre fichier appelé table. Cette clé est un pointeur contenant l'adresse de l'information dans le fichier.
INCORPORER Designer \s \* fusionformat
Table Fichier
File
Une file ex "file d'attente"contient une liste de travaux en attente que le système gère :
soit dans l'ordre d'arrivée avec une pile FIFOex "FIFO" (First In, First Outex "first in, first out") ou premier entré, premier sorti,
soit dans l'ordre inverse de leur arrivée avec une pile LIFOex "LIFO" (last in, first outex "last in, first out") ou dernier entré, premier sorti.
Liste chaînée selon un algorithme FIFO
Une liste peutêtre chaînée selon un algorithme de type pile FIFO où le pointeur sur la structure contient l'adresse de la structure successeur.
Le balayage de la liste est exécuté séquentiellement à partir de son premier élément.
INCORPORER Designer \s \* fusionformat
Le programme suivant permet une gestion de liste chaînée de tableaux de flottants.
#define SIZE 10
#define N 5
#include
int main(void)
{int i, nombre;
typedef struct chaine {float tab[SIZE]; struct chaine *suivant;} chaine;
chaine * debut, *next, *pred;
/* initialisation du premier élément de la liste chainée */
debut = (chaine *) calloc(1,sizeof(chaine));
printf("adresse initiale de la chaîne: %x\n",debut);
printf("valeur initiale de la chaîne:\n");
for(i=0;itab[i]);
printf("\n");
printf("debut->suivant = %x\n",debut->suivant);
/* remplissage du premier élément de la liste chainée */
for(i=0;itab[i]= (float)i;
/* allocation et initialisation de la mémoire pour la nouvelle structure */
next = (chaine *) calloc(1, sizeof(chaine));
/* initialisaton de la structure */
for(i=0; i< SIZE; i++) next->tab[i] = (float) 2*i;
next->suivant=(chaine *) NULL;
debut->suivant = (chaine *) next; /* chaînage des deux structures */
/* création et remplissage de plusieurs listes chaînées */
for(nombre=3;nombretab[i] = (float) nombre *i;
next->suivant=(chaine *) NULL;
pred->suivant=next; /* chaînage entre le prédécesseur et le successeur */
}
/* affichage des résultats */
printf("affichage des résultats\n");
next=debut;
while (next->suivant !=(chaine *) NULL)
{for(i=0; i< SIZE; i++) printf("%6.2f ",next->tab[i]);
printf("\n next->suivant = %x\n", next->suivant);
next = (chaine *) next->suivant;
}
for(i=0; i< SIZE; i++) printf("%6.2f ",next->tab[i]);
printf("\n next->suivant = %x", next->suivant);
exit(0);
}
// résultat
adresse initiale de la chaîne: a7c
valeur initiale de la chaîne:
0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
debut->suivant = 0
affichage des résultats
0.00 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00
next->suivant = aaa
0.00 2.00 4.00 6.00 8.00 10.00 12.00 14.00 16.00 18.00
next->suivant = ad8
0.00 3.00 6.00 9.00 12.00 15.00 18.00 21.00 24.00 27.00
next->suivant = b06
0.00 4.00 8.00 12.00 16.00 20.00 24.00 28.00 32.00 36.00
next->suivant = b34
0.00 5.00 10.00 15.00 20.00 25.00 30.00 35.00 40.00 45.00
next->suivant = b62
0.00 6.00 12.00 18.00 24.00 30.00 36.00 42.00 48.00 54.00
next->suivant = b90
0.00 7.00 14.00 21.00 28.00 35.00 42.00 49.00 56.00 63.00
next->suivant = 0
Liste chaînée selon un algorithme LIFO
Quand les pointeurs contiennent l'adresse de la structure précédente, ils permettent de constituer une liste chaînée dont les éléments sont accessibles par un algorithme de type LIFO.
Le balayage est séquentiel à partir du dernier élément de liste.
INCORPORER Designer \s \* fusionformat
Exercice 8.3 : écrire un programme de gestion d'une liste chaînée (LIFO) constituée de plusieurs tableaux de flottants.
Liste doublement chaînée
Une liste doublement chaînée est simultanément FIFO ou LIFO. Sa structure contient deux pointeurs : un pointeur de chaînage avant, un pointeur de chaînage arrière (exercice laissé au lecteur).
INCORPORER Designer \s \* fusionformat
Piles
Une pileex "pile" est une structure de donnéesex "structure de données" permettant la gestion de l'historique d'une séquence d'événements. Deux cas se présentent :
la gestion des interruptionex "interruption"s ou des branchementex "branchement"s durant lesquels il faut sauvegarder le numéro d'une interruption masquée ou l'adresse de l'instruction suivante de manière à pouvoir reprendre ultérieurement l'exécution du programme à cette adresse,
la transmission des arguments à l'appel d'une fonction ou d'une procédure.
Les informations contenues dans une pile sont toujours de même nature dans une pile donnée, mais elle peuvent être de types différents (variable, pointeur, enregistrement logique, etc...) dans des piles différentes.
L'événement le plus récent est situé au sommet de la pile, le plus ancien à la base. L'indice du sommet de la pile est contenu dans le registre pointeur de pileex "pointeur de pile" (stack pointerex "stack pointer").
INCORPORER Designer \s \* fusionformat
Structure de Pile
Il existe des piles matérielles et des piles logicielles. Une pile matérielleex "pile matérielle" est un circuit qui gère automatiquement le pointeur de pile. L'avantage est une grande rapidité dans l'utilisation, l'inconvénient est sa taille limitée. Cette structure, qui a attiré les concepteurs des premiers microprocesseurs, est maintenant délaissée. Une pile logicielleex "pile logicielle" est une zone réservée de la mémoire vive dont l'adresse en mémoire est décidée par le programmeur. Les dépassements sont possibles en cas d'erreur de programmation.
Opérations sur les piles
Les opérations de manipulation d'une pile sont l'empilementex "empilement" dont l'instruction est PUSHex "PUSH" (pousse ou dépose), le dépilementex "dépilement" supérieur dont l'instruction est POPex "POP" (prends ou retire), le dépilement inférieur dont l'instruction est POPDex "POPD" pour déposer ou retirer un des éléments sur le sommet ou sur la base de la pile permettant l'accès à la dernière ou la première information de la pile.
INCORPORER Designer \s \* fusionformat
Dépilement supérieur Dépilement inférieur
Exercice récapitulatif sur les listes
Nous allons étudier une méthode de tri par chaînage. Il s'agit, à partir d'une liste de noms saisis dans un ordre quelconque, de constituer une liste chaînée d'indices permettant de faire un parcourt par ordre alphabétique de la liste initiale.
Rappelons qu'une liste chaînée a la structure suivante :
INCORPORER Designer \s \* fusionformat
Soit le tableau liste, de N structures, chacune composée de deux champs :
la chaîne de caractères nom contiendra le nom saisi,
l'entier tsuc, contiendra l'indice, dans le tableau liste, du successeur (dans l'ordre alphabétique) du nom précédemment saisi, avec les conventions suivantes :
liste [0 ].tsuc pointe sur le premier nom, dans l'ordre alphabétique, de la liste,
liste [i ].tsuc = k, indice du successeur de liste [i].nom, dans l'ordre alphabétique, si k CARSPECIAUX 185 \f "Symbol"0,
liste [i ].tsuc = 0, si le i-ème nom saisi n'a pas de successeur dans l'ordre alphabétique.
Toute nouvelle saisie va modifier deux enregistrements du tableau liste. Soit les tableaux :
i-ème nom saisi liste [i ].nom liste [i].tsuc
0 2 premier nom de la liste
1 DUPOND 3, successeur de DUPOND
2 ALBERT 1, successeur de ALBERT
3 MARTIN 0 n'a pas de successeur
L'introduction du nom ALAIN va modifier le tableau liste[].tsuc selon le chaînage suivant :
i-ème nom saisi liste [i ].nom liste [i ].tsuc
0 4
1 DUPOND 3
2 ALBERT 1
3 MARTIN 0
4 ALAIN 2
L'algorithme de création de la liste chaînée, pour déterminer la valeur du prédécesseur et du successeur du nom saisi, est le suivant.
Notations
Soient j et pred les indices respectifs du candidat successeur et du candidat prédécesseur du ième nom saisi.
Au départ :
pred = 0; j:= liste [0 ].tsuc
Le premier candidat prédécesseur est initialisé à 0. Il est comparé au prédécesseur du premier nom classé dans l'ordre alphabétique. Il y a deux possibilités :
liste [i ].nom s'intercale dans la liste
Dans ce cas :
CARSPECIAUX 36 \f "Symbol" j0 tel que liste [pred ].nom CARSPECIAUX 60 \f "Symbol" liste [i ].nom CARSPECIAUX 163 \f "Symbol" liste [j0 ].nom
et
liste [pred ].tsuc = i;
liste [i ].tsuc = j0;
liste [i ].nom ne s'intercale pas dans la liste.
C'est donc le (nouveau) dernier élément de la liste, dans l'ordre alphabétique. Il n'aura pas de successeur. Soit j0 l'indice du dernier nom par ordre alphabétique de la liste avant l'insertion du nouveau nom dans la liste.
Représentation graphique
Soit Li la liste de noms à l'étape i, alors Li = Li-1 CARSPECIAUX 200 \f "Symbol" liste [i ].nom
Li-1 liste [i ].tsuc Li
n1 p1 n1
... ...
... CARSPECIAUX 200 \f "Symbol" ... =
... ...
ni-1 pi-1 ni-1
ni
Evolution du tableau liste.tsuc
Voyons l'évolution du tableau liste [].tsuc dans les deux cas.
Premier cas : le nom s'intercale dans la liste.
En utilisant les relations cidessus, on obtient :
Li-1 liste[].tsuc Li liste[].tsuc
0 0
n1 p1 n1 p1 inchangé
... ... ... ...
nk j0 nk i
... ...
ni-1 pi-1 ni-1 pi-1
ni j0
Avant Après
Deuxième cas : le nom ne s'intercale pas dans la liste :
On a la représentation suivante :
Li-1 liste[].tsuc Li liste[].tsuc
0 0
n1 p1 n1 p1 inchangé
... ... ... ...
nj0 0 nj0 i
... ... ...
ni-1 pi-1 ni-1 pi-1
ni 0
Avant Après
Analyse
Avant l'insertion, on a les relations :
liste [j0 ].tsuc = 0
liste [j ].nom CARSPECIAUX 60 \f "Symbol" liste [j0 ].nom pour tout j = 1,... , i-1
Après l'insertion, on a les relations :
liste [i ].tsuc = 0 = liste [j0 ].tsuc
liste [j0 ].tsuc = i
Arrêt du calcul
cas 1 : dès qu'il existe j0 tel que liste [i ].nom > liste [j ].nom
cas 2 : dès que j0= 0
Algorithme
L'algorithme se décompose en deux phases :
Phase 1
Recherche de la position
Initialisation de la recherche : pred := 0; j := liste[0].tsuc
Recherche effective
Arrêt de la recherche : deux possibilités :
dès qu'on a déterminé j0 tel que liste [j0 ].nom CARSPECIAUX 179 \f "Symbol" liste [j ].nom > liste [pred ].nom
ou dès que j = 0 ce qui implique que ni n'a pas de successeur.
La négation de ces deux conditions s'écrit :
j CARSPECIAUX 185 \f "Symbol" 0 ou liste[j].nom < liste[i].nom
Phase 2
Modification des deux champs à modifier de liste[].tsuc par les formules :
liste[i].tsuc := j
liste[pred].tsuc := i
Ces formules sont valables dans les deux cas ce qui simplifie l'écriture de l'algorithme.
Programme
Initialisation
lire N, dimension du tableau liste
liste[i].tsuc = 0, pour tout i= 0,N
liste[i].NOM = "" pour tout i = 1,N
i := 1
Tant que(i CARSPECIAUX 60 \f "Symbol" N et liste [i].nom CARSPECIAUX 185 \f "Symbol""") faire
saisir liste [i ].nom;
pred := 0; j := liste [0 ].tsuc; & initialisation &
Recherche
Tant que (j CARSPECIAUX 185 \f "Symbol" 0 et (liste [j ].nom CARSPECIAUX 60 \f "Symbol" liste [i ].nom)) faire
pred := j; & j : nouveau candidat prédécesseur &
j := liste [j ].tsuc; & nouveau candidat successeur &
fin_faire
liste [i ].tsuc := j; liste [pred ].tsuc = i;
& modification de liste [ ].tsuc
fin_faire
Le tri alphabétique est trivial.
j = liste [0 ].tsuc;
Tant que (j CARSPECIAUX 185 \f "Symbol" 0) faire
imprimer liste [j ].nom;
j = liste [j ].tsuc;
fin_faire
Programmation
La fonction saisie saisit au clavier la liste des individus et la stocke dans un fichier.
La fonction lire permet, à partir du fichier des individus, créer le tableau des successeurs. La comparaison de deux chaînes de caractères est réalisée par la fonction st qui retourne, à partir de la fonction de la librairie C strcmp (prototype dans string.h) , un nombre entier, utilisable dans le test de fin de recherche.
La fonction tri réalise le tri alphabétique.
Version tableau
#include
#include
#define MAX 20 /* nombre maximum de noms */
#define ever (;;) /* boucle de saisie */
typedef struct {char nom [20 ]; int tsuc;} pers;
void main(void)
{int i,k; pers liste [MAX ];
/* prototypes */
void saisie(void), tri(pers [ ]);
int lire(pers [ ]);
saisie(); /* saisie de la liste dans un fichier */
k = lire(liste); /* constitution du tableau des successeurs*/
printf ("liste non triée et indice du successeur\n");
for(i=0;i_wcnt>=0?((int)(*(p)->_ptr++=(c))) : _flsbf((c),p))
#define putchar(c) putc(c,stdout)
int main(void)
{int c;
while ((c = getchar()) != EOF) putchar(c);
exit(0);
}
getchar()
{static char buf [BUFSIZE ];
static char *bufp = buf;
static int n = 0;
if (n == 0)
{n = read(0,buf,BUFSIZE);
bufp = buf;
}
return((--n >=0) ? *bufp++ & CMASK : EOF);
}
Fermeture
L'appel système close ferme les fichiers ouverts.
Synopsis
#include
int close(int fd);
Exemple
L'appel système closeex "close" supprime la connexion entre un fichier ouvert et son descripteur assurant ainsi la fermetureex "fermeture" des fichiers. Bien entendu, le fichier sera réutilisable par la suite.
Déplacement dans un fichier
Les fichiers ordinaires sont vus comme des fichiers séquentiels par le système d'exploitation UNIX avec un pointeur sur l'octet courant. Toutefois, l'accès aléatoireex "accès aléatoire" aux différents octets est possible avec l'appel système lseekex "lseek".
Synopsis
off_t int lseekEX "lseek"(int fd, off_t offset , int origine);
Exemple
La variable offset modifie le pointeur sur l'octet courant du fichier dont le descripteur est fd; la nouvelle position (en octets) est calculée de la façon suivante :
si origine = 0, le déplacement est absolu et croissant par rapport l'origine du fichier,
si origine = 1, le déplacement est relatif et croissant par rapport la position courante,
si origine = 2, le déplacement est absolu et croissant par rapport la fin du fichier (mode ajout).
La norme POSIX a remplacé les valeurs 0, 1, 2 par les constantes SEEK_SET, SEEK_CUR, SEEK_END.
Exemple : lseek(fd,0L,2);
Appels de commandes de l'interprète
On peut, depuis un programme écrit en C, appeler des commandes de l'interprète par utilisation de l'appel système systemex "system".
Synopsis
#include
int system(const char *programme);
Exemple : le programme en cours est suspendu. La commande spécifiée par la chaîne de caractères *programme est exécutée si elle existe (code de retour différent de zéro). Dans le cas contraire, un code de retour nul est retourné.
Exemples
a) Voici un programme qui permet l'affichage de la date dans l'environnement MS/DOS.
#include
int main(void)
{system("date");
exit(0);
}
b) ce programme permet d'exécuter plusieurs commandes (sans argument).
#include
int main(argc,argv)
int argc;
char *argv[];
{int i;
for(i= 0; i 0)
if ((fp = fopen(*++argv,"r")) == NULL)
{printf("cat : can't open %s\n",*argv); break;}
else
{filecopy(fp);fclose(fp);}
exit(0);
}
void filecopy(FILE *fp) /* recopie du fichier fp sur la sortie standard*/
{int c;
while ((c = getc(fp)) != EOF) putc(c,stdout);
}
Exercice 8.1 : corriger la version précédente de la commande cat pour que les messages d'erreurs soient affichés dans le fichier stderr, différent du fichier cible.
Exercice 8.2 : écrire un programme de recopie d'un fichier qui envoie les messages d'erreurs dans le fichier stderrr, différent du fichier cible.
Ecriture et lecture de lignes de caractères
On utilise les fonctions fgetsex "fgets" et fputsex "fputs" dont la syntaxe est :
Synopsis
#include
char * fgets(char *chaine, int n, FILE *fp);
int fputs(char *chaine, FILE *fp);
Exemple
La fonction fgets lit des caractères depuis le flot pointé par le descripteur fp dans la zone mémoire pointée par chaîne soit jusqu'au n-1 ième caractère, soit jusqu'à la fin du fichier, soit jusqu'au caractère '\n'.
La fonction fputs écrit la chaîne de caractères pointée par chaîne dans le flot pointé par le descripteur fp. Le terminateur de chaîne n'est pas écrit à la fin de la chaîne. Le caractère '\n' n'est pas écrit à la fin de la ligne.
Exemple : programme de recopie d'un fichier ligne par ligne.
#include
#define LONG 132
int main(void) /* fichier d'entrée : descripteur fp1 */
{FILE *fp1,*fp2; /* fichier de sortie : descripteur fp2 */
char *fgets(char *, int , FILE*);
int fputs(char *, FILE *);
FILE *fopen(char *FILE, char *);
char chaine[LONG];
fp1 = fopen("entree" ,"r"); /* fichier d'entrée */
fp2 = fopen("sortie" ,"w");/* fichier de sortie */
while(fgets(chaine,LONG, fp1) != NULL) fputs(chaine,fp2);
fclose(fp1);
fclose(fp2);
}
Ecriture et lecture d'items
Un itemex "item" est une séquence de bits non nécessairement terminée par l'octet 0. On ne considère donc pas son organisation ou son format.
Les fonctions de lecture et d'écriture d'items sont respectivement fread et fwrite.
Synopsis
size_t fread(void *buf, size_t l, size_t n, FILE *fp);
size_t fwrite(const void *buf,size_t l, size_t n, FILE *fp);
Exemple
La fonction freadex "fread" charge n items de longueur l depuis le fichier pointé par fp, à l'adresse mémoire pointée par le pointeur buf. La fonction fwriteex "fwrite" effectue l'opération duale. Les valeurs retournées sont respectivement le nombre d'items lus et écrits.
INCORPORER Designer \s \* fusionformat
Gestion du tampon d'entrée/sortie
Allocation d'un nouveau tampon
Il est possible d'allouer de nouveaux tampons par utilisation de la fonction setvbuf.
Synopsis
#include
int setvbufEX "setvbuf"(FILE *ptr, char * tampon, int mode, size_t taille);
Description
Association d'un tampon au fichier. L'argument mode spécifie le critère de vidage du tampon :
_IOFBF vidage tampon plein.
_IOLBF vidage dès que le tampon contient une ligne.
_IONBF vidage permanent.
La norme POSIX définit la fonction simplifiée suivante :
#include
int setbufEX "setbuf"(FILE *ptr, char * tampon, size_t taille);
Vidage du tampon
L'appel système fflushex "fflush" provoque l'écriture effective de toutes les données contenues dans le tampon d'entrée/sortie buf associé au flot pointé par fp non encore écrites sur le cachedisque. La primitive fsyncex "fsync" provoque l'écriture effective du contenu du cache sur le disque. Il est ainsi possible d'écrire des flots de données non terminées par le caractère \n.
Synopsis
#include
int fflushEX "fflush"(FILE *fp);
Description
Vidage du contenu du tampon associé au fichier dont le descripteur est fp.
La fonction fflush retourne la valeur zéro si l'écriture s'est déroulée normalement, EOF sinon.
Appels indirects
La fonction exitex "exit", la fonction closeex "close" appellent la fonction fflush.
Cas des fichiers standards
Quand la sortie standard matérialise un terminal, le tampon associé est géré en mode ligne.
Quand l'entrée et la sortie standard sont associés à un terminal (processus interractif), une demande de lecture sur l'entrée standard provoque le vidage du tampon associé à la sortie standard.
Quand la sortie standard n'est pas associée à un terminal, le tampon est géré en mode "vidage tampon plein".
Quand le fichier standard des messages d'erreur est associé à un terminal et n'a pas été redirigé par un appel réopen, le tampon est vidé systématiquement.
Quand le fichier standard des messages d'erreur est associé à un terminal et est redirigé, le tampon est géré en mode ligne.
Quand le fichier standard des messages d'erreur n'est pas associé à un terminal et est redirigé sur un fichier ordinaire, le tampon est géré en mode "tampon plein".
Exemple
Analyser les programmes suivant :
/* fichier stderr1.c */
#include
int main(void)
{fprintf(stderr,"abcd\n"); fprintf(stderr,"xyz"); while(1);}
/* à l'exécution, tous les caractères abcdxyz sont affichés */
/* fichier stderr2.c */
#include
int main(void)
{freopen("/dev/tty","w",stderr);
fprintf(stderr,"abcd\n"); fprintf(stderr,"xyz"); while(1);}
/* à l'exécution, seuls les caractères abcd sont affichés */
/* fichier stderr3.c */
#include
int main(void)
{freopen("toto","w",stderr);
fprintf(stderr,"abcd\n"); fprintf(stderr,"xyz"); while(1);}
/* à l'exécution, toto est vide */
/* fichier stderr4.c */
#include
int main(void)
{freopen("toto","w",stderr);
fprintf(stderr,"abcd\n"); fprintf(stderr,"xyz");}
/* à l'exécution, toto contient abcd */
Exercice sur la gestion du tampon d'entrée/sortie
1°) Ecrire un programme qui mette en évidence l'existence du tampon d'affichage (ligne par ligne).
/* fichier stdout1.c */
#include
int main(void)
{printf("abcd\n"); printf("xyz"); while(1);}
/* A l'exécution, seuls, les caractères abcd sont affichés */
2°) Vérifier que la saisie par scanf va modifier le comportement du tampon associé à stdout.
/* fichier stdout2.c */
#include
int main(void)
{int n;
printf("abcd\n"); printf("xyz"); scanf("%d",&n); while(1);}
/* Tous les caractères abcd
xyz
sont affichés */
3°) Voici un autre effet du non vidage du tampon.
/* fichier stdout3.c */
/* affichage et redirection corrects si la boucle while n'est pas exécutée. */
/* dans le cas contraire, le fichier toto est vide */
#include
int n;
int main(void)
{freopen("toto","w",stdout); /* redirection de stdout sur toto */
/* redirection de stdout sur le fichier toto */
printf("abcd\n"); printf("xyz");
scanf("%d",&n);
/* while(1);*/}
Accès direct à l'information
L'indicateur de positionex "indicateur de position" d'un fichier (texte ou binaire) indique la position du pointeur sur l'octet courant. Sur un fichier binaire, il est incrémenté lors de la lecture ou de l'écriture d'un caractère. Celui d'un fichier texte est modifié suivant l'implémentation locale.
Gestion de l'indicateur de position
Les fonctions fgetposex "fgetpos" ou ftellex "ftell" lisent l'indicateur de position. Les appels fsetposex "fsetpos" ou fseekex "fseek" en permettent une modification directe.
Les fonctions fseekex "fseek" et ftellex "ftell" utilisent un indicateur de position de type long.
Les fonctions fsetpos et fgetpos utilisent un indicateur de position de type fpos_tex "fpos_t" avec des fichiers dont l'indicateur de position a une valeur absolue trop grande pour être de type long.
Synopsis
#include
int fseek(FILE *fp, long int offset, int mode);
Exemple
La fonction fseekex "fseek" modifie la position du pointeur courant dans le fichier avec les conventions :
fp est le descripteur du fichier.
offset définit la position (en nombre d'octets) de la prochaine entrée/sortie à partir
du début du fichier si mode = 0,
de la position courante si mode = 1,
de la fin du fichier si mode = 2.
Synopsis
#include
int fsetpos(FILE *fp, const fpos_t * position);
Exemple
La fonction fsetposex "fsetpos" modifie la position de la prochaine entrée/sortie dans le fichier fp.
Repositionnement en début de fichier
Synopsis : void rewindex "rewind"(FILE *fp);
Exemple
La fonction rewind permet de se repositionner au début du fichier par modification de l'indicateur de position.
Calcul du déplacement
Synopsis
#include
long int ftell(FILE *fp);
Exemple
La fonction ftellex "ftell" retourne le déplacement de l'octet courant par rapport au début du fichier associé.
Gestion des conditions d'erreurs
Il y a deux indicateurs de conditions d'erreur : l'indicateur de fin de fichier et l'indicateur d'erreur d'un flot dont les valeurs sont respectivement accessibles par les appels systèmes feofex "feof" et ferrorex "ferror".
Synopsis
#include
int feof(FILE *fp), ferror(FILE *fp);
Exemples
La fonction feofEX "feof" retourne zéro tant que la fin du fichier pointé par fp n'est pas atteinte. La fonction ferror retourne le code (entier) de l'erreur ou zéro (absence d'erreur).
Voici un programme qui réalise la saisie, puis l'écriture immédiate dans un fichier, d'une chaîne de caractères contenant une structure composée de deux chaînes de caractères et d'un entier.
Saisie : la fonction saisie
La fonction utilisée pour la saisie étant gets, il est nécessaire de convertir le troisième champ en un entier avec la fonction atoi, puis d'utiliser la fonction fflush pour vider le tampon.
La fonction fwrite est utilisée pour l'écriture dans le fichier.
La structure est constituée des trois champs
char nom[30], prénom[30], int tel;
Relecture : la fonction voir
Cette fonction relit ce fichier et affiche son contenu à l'écran. La lecture est exécutée enregistrement par enregistrement par la fonction fread.
#include
#include
/* nécessaire pour atoi */
#include
/* définition de la structure pers */
struct pers_type {char nom[30]; char prenom[20]; int tel; };
typedef struct pers_type pers;
#define ever (;;)
int main(void)
{void saisie(void), voir(void); /* prototypes */
saisie();
voir();
}
void saisie(void) /* saisie des données */
{FILE *fp;
pers accueil={"","",0}, vide= {"","",0};
char tel[20];
fp=fopen("personnel","a");
for ever
{printf("Donnez le nom : ");
gets (accueil.nom);
if (!strlen(accueil.nom)) break; /* fin de saisie */
printf("Donnez le prenom : ");
gets(accueil.prenom);
printf("Donnez le téléphone : ");
gets(tel);
accueil.tel=atoi(tel); /* conversion de chaîne par la fonction atoi*/
fwrite ((char *)&accueil, sizeof(accueil),1,fp);
fflush(fp); /* vidage du tampon après chaque saisie */
accueil=vide; /* réinitialisation entre deux saisies*/
}
fclose(fp); /* fermeture du fichier */
}
void voir(void)
{FILE *fp;
pers accueil;
printf(" Voici le fichier lu : \n");
fp=fopen("personnel","r");
while (fread((char*)&accueil, sizeof(accueil),1,fp))
/* chargement d'un enregistrement de longueur sizeof(accueil) du fichier dont */
/* l'étiquette logique est fp dans le tampon mémoire pointé par accueil*/
printf("%-30.30s%-20.20s%d\n", accueil.nom, accueil.prenom, accueil.tel );
fclose(fp);
}
//résultat
Donnez le nom :
Voici le fichier lu :
ADELE Vanessa 77
SYLVIE Andrée 876
SYLVIE Luise 666
PINSON Caius 66
GIFFOU Jorges 765
LANGOISSE Guytou 666
JEAN Sans peur 666
Lecture et écriture en mémoire
Ecriture : la fonction sprintfex "sprintf"
Cette fonction est similaire à la fonction printf. La seule différence est qu'elle met le résultat dans une chaîne de caractères au lieu de l'afficher à l'écran.
Synopsis : sprintf(chaine, spécification_de_format, arg1,..., argn);
Lecture : la fonction sscanfex "sscanf"
Cette fonction est similaire à la fonction scanf. La seule différence est qu'elle lit les données dans une chaîne de caractères.
Synopsis : sscanf(chaine, spécification_de_format,&arg1,...,&argn);
Exemple
#include
int main(void)
{int j,i = 10;
char chaine[10];
sprintf(chaine,"%d\n",i);
printf(" résultat %s\n",chaine);
sscanf(chaine,"%d",&j);
printf(" j = %d\n",j);
}
//résultat
j = 10
Récapitulation des instructions d'entrée/sortie
La norme ANSI définit les fonctions suivantes d'entrée/sorties regroupées dans le fichier stdio.h.
Entrées/sorties par caractères
fgetcex "fgetc" lecture d'un caractère dans un fichier,
fgetsex "fgets" lecture d'une ligne dans un fichier,
fputcex "fputc" écriture d'un caractère dans un fichier,
fputsex "fputs" écriture d'une ligne dans un fichier,
getcex "getc" lecture d'un caractère dans un flot,
getcharex "getchar" lecture d'un caractère dans l'entrée standard,
getsex "gets" lecture d'une chaîne de caractères dans l'entrée standard,
putcex "putc" écriture d'un caractère dans un flot,
putcharex "putchar" écriture d'un caractère dans l'entrée standard,
putsex "puts" écriture d'une chaîne de caractères dans l'entrée standard.
Entrées/sorties par item
freadex "fread" lecture de données dans un fichier,
fwriteex "fwrite" écriture de données dans un fichier.
Entrées/sorties avec spécifications de format
fprintfex "fprintf" écriture d'un texte formaté dans un fichier,
fscanfex "fscanf" lecture d'un texte formaté dans un fichier,
printfex "printf" écriture d'un texte formaté dans la sortie standard,
scanfex "scanf" lecture d'un texte formaté dans l'entrée standard,
sprintfex "sprintf" écriture d'un texte formaté dans une chaîne de caractères,
sscanfex "sscanf" lecture d'un texte formaté dans une chaîne de caractères.
Les fonctions Qsort et Bsearch
Avant de poursuivre, voyons la définition des fonctions qsort et bsearch.
qsort
La fonction qsort trie un tableau.
Synopsis
#include
void qsort(void *tableau, size_t n, size_t t,
int(*comparaison)(const void *arg1, const void *arg2));
Exemple
La variable tableau pointe sur la base du tableau de n éléments, chacun est composé de t octets, à trier par ordre croissant. En général, tableau est un tableau de pointeurs et t la taille des objets pointés.
La variable comparaison est un pointeur sur une fonction de comparaison de deux composantes du tableau. Elle retourne 0 si les deux éléments comparés sont identiques, un nombre positif si l'élément pointé par arg1 est supérieur à arg2, un nombre négatif s'il est inférieur.
bsearch
La fonction bsearch recherche un item donné d'un tableau trié préalablement par la fonction qsort.
Synopsis
#include
void *bsearch(const void *item, const void *tableau, size_t n, size_t p,
int(*comparaison)(const void *arg1, const void *arg2));
Exemple
La variable item pointe sur l'objet recherché.
La variable tableau pointe sur la base d'un tableau préalablement trié (par tsort) par ordre croissant de n éléments, dont chacun est composé de p octets.
La variable comparaison est un pointeur sur une fonction de comparaison de l'item recherché à chacune des composantes du tableau. Cette fonction devra retourner 0 si les deux éléments comparés sont identiques, un nombre positif si l'élément pointé par arg1 est supérieur à arg2, un nombre négatif s'il est inférieur.
Exercice récapitulatif
L'exercice proposé permet d'utiliser les fonctions présentées cidessus.
Enoncé
On se propose d'écrire un programme de gestion d'un agenda. Pour cela il est nécessaire de disposer des fonctionnalités suivantes :
a) saisie des informations : fonction saisie;
b) lecture des informations : fonction voir;
c) tri des informations : fonction tri;
d) recherche des informations : fonction cherche.
Un menu devra permettre d'accéder à ces différentes fonctions (fonction main).
Les fonctions de tri et de recherche seront facilitées si on utilise les fonctions de la bibliothèque qsortex "qsort" et bsearchex "bsearch".
Le prototype des structures sera défini par la structure globale pers, contenant la description des champs nom, prénom, et numéro de chaque variable associée un individu.
Il reste un dernier problème à résoudre. Le tri et la recherche des informations étant effectués en mémoire, il est nécessaire d'y copier le contenu du fichier. Pour être certain de disposer de la place mémoire nécessaire, il faudra utiliser les fonctions calloc et free.
Programme
/* création et consultation d'un petit agenda */
#include /* définition des fonctions d'entrée/sortie */
#include /* pour les fonctions calloc, qsort, bsearch */
#include /* pour les fonctions de manipulation de chaînes */
/* définition de la structure pers */
#define BUFSIZ 512
#define MAXNOM 30
#define MAXPRE 20
#define MAXTEL 11
typedef struct {char nom[MAXNOM]; char prenom[MAXPRE]; int tel;} pers;
#define ever (;;)
int main(void) /* menu général */
{/* quelques prototypes */
void saisie(void), tri(void), cherche(void), exit(int);
int voir(void);
char buf[BUFSIZ];/* longueur maximum de la chaîne saisie */
printf("GESTION DU PERSONNEL");
for ever
{printf("\n\n\n\t1-\tSAISIE \n");
printf("\t2-\tVISION \n");
printf("\t3-\tTRI \n");
printf("\t4-\tRECHERCHE \n");
printf("\t5-\tSORTIE DU PROGRAMME\n");
printf("\n\n---->");
gets(buf);/* saisie des données dans la chaîne buf */
switch(buf[0])
{case '1' : saisie(); break;
case '2' : voir(); break;
case '3' : tri(); break;
case '4' : cherche(); break;
case '5' : exit(1);
default : printf("choisir entre 1, 2, 3, 4, 5\n");
}
}
}
FILE * ouvre (char *chemin, int *nbelem)
/* ouverture du fichier */
/* calcul du nombre d'enregistrements */
/* retour du descripteur associé */
{FILE *fp;
/* ouverture du fichier en lecture */
fp=fopen(chemin,"r");
/* positionnement sur le dernier enregistrement du fichier*/
fseek(fp,0L,2);
/* calcul du nombre total d'enregistrements */
*nbelem= (int) ftell(fp)/sizeof (pers);
/* repositionnement en début de fichier */
rewind(fp);
return(fp);
}
void cherche(void) /* recherche d'un enregistrement dans la base */
{/* variables locales */
int nbelem=0;
FILE *fp;
pers *pt, *mem, accueil;
char buf[BUFSIZ];
/* prototypes */
void *bsearch(const void *, const void *, size_t,size_t,int (*)(const void *, const void *));
int compar(const void *, const void *);
char *strncpy(char*, const char*,size_t);
FILE *ouvre(char *, int *);
void *calloc(size_t,size_t);
printf("Donnez le nom à rechercher dans la base\n--->");
gets(buf); /* saisie du nom recherché */
/* transfert de la chaîne saisie en mémoire */
strncpy(accueil.nom,buf,MAXNOM-1);
fp = ouvre("pers_trie",&nbelem);
/* allocation de la mémoire nécessaire pour contenir l'ensemble */
/* des enregistrements du fichier par la fonction calloc */
mem=(pers *) calloc(nbelem,sizeof (pers));
fread((char *)mem, sizeof (pers), nbelem, fp); /* lecture du fichier */
fclose(fp); /* fermeture du fichier */
/* recherche en mémoire */
pt=(pers *) bsearch((char *)&accueil,(char *)mem, nbelem, sizeof(pers), compar);
if (pt)
printf("\n%-*.*s%-*.*s%-d",
MAXNOM,MAXNOM,pt->nom, MAXPRE, MAXPRE, pt->prenom, pt->tel);
else printf("le nom %s n'est pas dans la base\n", accueil.nom);
/* libération de l'espace mémoire utilisé */
free((char *)mem);
}
int compar(const void * ch1, const void * ch2) /* comparaison de deux items */
{pers *s1 = (pers*) ch1, *s2= (pers*) ch2;
return(strncmp(s1->nom,s2->nom,MAXNOM));
}
void saisie(void) /* saisie d'un enregistrement et écriture */
{FILE *fp;
pers accueil={"","",0}, vide= {"","",0};
char tel[MAXTEL];
char buf[BUFSIZ];
fp=fopen("personnel","a");
for ever
{printf("Donnez le nom : ");
gets(buf); strncpy(accueil.nom,buf,MAXNOM-1);
if(! strlen(accueil.nom)) break; /* fin de la saisie */
printf("Donnez le prenom : ");
gets(buf); strncpy(accueil.prenom,buf,MAXPRE-1);
printf("Donnez le téléphone : ");
gets(buf); strncpy(tel,buf,MAXTEL-1);
accueil.tel=atoi(tel); /* conversion de chaîne par l'appel de la fonction atoi */
fwrite ((char *) &accueil, sizeof(accueil), 1, fp); /* écriture dans le fichier */
accueil=vide; /* réinitialisation pour la saisie suivante */
}
fclose(fp); /* fermeture du fichier */
}
void tri(void) /* fonction de tri en mémoire par appel de la fonction qsort */
{/* déclaration des variables locales */
FILE *fp;
int nbelem; /* nombre d'items du fichier */
pers *mem; /* nom du tampon mémoire */
/* prototypes */
void qsort(void *, size_t, size_t, int (*)(const void *, const void *));
int comp(const void *, const void *);
FILE* ouvre( char *, int * );
void *calloc(size_t,size_t);
int strncmp(const char *, const char *, size_t);
fp=ouvre("personnel",&nbelem); /* ouverture du fichier */
mem =(pers *) calloc(nbelem,sizeof(pers)); /* allocation dynamique */
fread((char *)mem,sizeof(pers),nbelem,fp); /* lecture des items */
fclose(fp); /* fermeture du fichier */
qsort((char *) mem,nbelem,sizeof (pers),comp); /* tri en mémoire */
fp=fopen("pers_trie","w"); /* ouverture du fichier résultat */
fwrite((char *) mem, sizeof (pers), nbelem, fp); /* écriture du tableau trié */
fclose(fp);
free((char *) mem); /* libération de la mémoire allouée */
}
int comp(const void * c1, const void * c2) /* fonction de comparaison de deux items */
{const pers * s1 = (pers*) c1, *s2 = (pers *) c2;
int ret;
if (ret=strncmp((const char *) s1->nom, (const char *) s2->nom,(size_t) MAXNOM))
return(ret);
return(strncmp((const char *) s1->prenom, (const char *) s2->prenom,MAXPRE));
}
int voir(void) /* lecture de la base */
{FILE *fp;
pers accueil;
char buf[BUFSIZ];
printf("Voulez-vous voir le fichier : \n");
printf("\t1-\tle fichier \"personnel\"\n");
printf("\t2-\tle fichier \"pers_trie\"\n");
printf("--->");
gets(buf);
if (buf[0] == '1') strcpy(buf,"personnel");
else
if (buf[0] == '2') strcpy(buf,"pers_trie");
else return(0);
fp=fopen(buf,"r");
while(fread( (char*) &accueil, sizeof(accueil),1,fp))
printf("\n%-*.*s%-*.*s%-d", MAXNOM,MAXNOM,accueil.nom,
MAXPRE,MAXPRE,accueil.prenom,accueil.tel);
fclose(fp);
return(1);
}
LES OUTILS DE DEVELOPPEMENT SOUS UNIX
La chaîne de développement
La chaîne de développement est constituée par l'ensemble des outils constituant le support de programmation et permettant de créer des programmes exécutables. Dans l'environnement UNIX SYSTEM V Release 4, ils sont de quatre types :
édition et génération d'un programme exécutable,
analyseur syntaxique et sémantique des programmes,
outils de génie logiciel,
utilitaires divers.
Génération d'un programme exécutable
Nous présentons dans le présent paragraphe les principes généraux de transformation des programmes écrits dans un langage évolué en langage machine.
Cycle de développement
Pour s'exécuter, tout programme doit suivre le cycle suivant :
écriture du programme dans un langage évolué : c'est le code source,
transformation du code source en langage machine : c'est la compilation,
recherche par l'éditeur de liens dans les diverses bibliothèques des variables dont les référenceex "référence"s sont non satisfaites à la compilation pour la génération définitive du code exécutableex "code exécutable"
La démarche est similaire pour l'écriture de programme en langage d'assemblage.
Compilation et édition de lien
L'adressage symboliqueex "adressage symbolique" désigne des adresses mémoire ou des valeurs par des symboles alphanumériquesex "alphanumérique" appelés respectivement étiquetteex "étiquette"s (labelex "label"s) ou symboleex "symbole"s. Le programmeur peut ainsi associer une adresse en mémoire avec son contenu. On fait référenceex "référence" à un symbole quand il est utilisé dans une instruction du programme. Cette référence est une référence interneex "référence interne" si le symbole est défini dans le programme, référence externeex "référence externe" sinon. Si on fait référence à un symbole avant de l'avoir défini explicitement, on dit que la référence est une référence en avantex "référence en avant". La correspondance entre un symbole et son adresse est assurée par la table des symbolesex "table des symboles". Les références sont satisfaites quand il est possible d'associer un symbole à son adresse à partir de la table des symboles.
Pour que les références puissent être satisfaites, il est nécessaire à la plupart des compilateurs de procéder en deux passeex "passe"s :
passe 1 : construction de la table des symboles,
passe 2 : achèvement de la table des références en avant, incomplète à la première passe.
Langage machine
Le langage machine est spécifique au calculateur. Toutes les instructions en langage machine sont représentées par une suite de 0 et 1 sous la forme :
CO CA AD
code condition zone
opération d'adressage adresse
Exemple
Soit l'instruction (codée ici en hexadécimal et en binaire) sur 32 bits
Hexadécimal 10 25 0208
Binaire 0001 0000 0010 1001 0000 0010 0000 1000
Assembleur
Programmer directement en langage machine est pratiquement impossible. C'est pourquoi un programme de traduction des instructions écrites en langage d'assemblage (le code sourceex "code source") en langage machine, en établissant une bijection entre les instructions "source" et les instructions "machine", directement interprétables par la machine pour laquelle il est conçu. L'assembleurex "assembleur" est aux langages d'assemblage ce qu'est un compilateur aux langages évolués. Il met à la disposition du programmeur plusieurs types de fonctions :
les instructions d'assemblageex "instructions d'assemblage",
les directives d'assemblageex "directives d'assemblage", destinées à faciliter l'écriture du programme.
Mise au point des programmes sources
L'environnement standard d'un utilisateur dans un système UNIX est composé de commandes publiques (assembleur, compilateur, interprètes de commandes, etc...) dont les répertoires d'implémentation sont /bin ou /usr/bin.
Il existe trois types d'outils : les éditeurs de textesex "éditeurs de textes", les outils de mise en forme, et l'analyseur syntaxiqueex "analyseur syntaxique" lintex "lint".
Editeurs de textes
Il existe de nombreux éditeurs de textes sous UNIX. Le seul qui soit un standard est vi. C'est le plus critiqué car il est peu ergonomique. Il est pourtant très puissant.
Les principaux autres éditeurs sont edex "ed", éditeur ligne, emacsex "emacs", xeditex "xedit", éditeur multifenêtres sous XWindowex "XWindow".
Outil de mise en forme
La mise en forme d'un fichier source d'un programme C peut être complétée par l'utilisation de l'enjoliveur de programmesex "enjoliveur de programmes" C cbex "cb" (C beautifulerex "C beautifuler").
Synopsis : cb fichier.c
Le fichier résultant est affiché sur la sortie standard. Il peut bien évidemment être redirigé.
Analyseur syntaxique
La vérification de la grammaire d'un programme source écrit en C est réalisé par le programme lintex "lint", dont les règles grammaticales sont plus strictes que celles du compilateur C lui même. Il renvoit les messages d'erreurs du compilateur C ainsi que des messages d'avertissement spécifiques lorsqu'il détecte des incohérences de définition ou des problèmes éventuels de portabilité.
Synopsis : lint fichier.c
L'utilisation systématique d'un tel outil est recommandée avec comme objectifs l'élimination progressive de tous les messages d'avertissement (warning) ou d'erreur.
Le compilateur C
Evolution du langage C
Le langage C a beaucoup évolué depuis sa création. Le C traditionnel, appelé C Kernighan et Ritchie, du nom de ses concepteurs, le C normalisé par l'ANSI sous la norme X3J11, puis intégré à l'ISO. Il existe donc des options de compilation qui permettent d'utiliser soit le C K&R, soit le C ANSI. Il faut, quand c'est possible, utiliser la norme ANSI. C'est la seule qui garantisse la portabilité des applications.
Format des fichiers
Un programme source écrit en C se présente sous la forme d'un fichier texte dont le nom se termine par le suffixe.c. D'une façon générale, les principaux suffixes ont la signification suivante :
.a bibliothèques de fichiers compilés et édités (édition de lien statique),
.c programme source C,
.f programme source Fortran,
.h fichier en tête de déclarations de variables globales et de définition de constantes en C ou C++,
.i fichier source intermédiaire produit par le préprocesseur,
.o fichier binaire objet produit à la fin d'une compilation ou d'un assemblage,
.p programme source pascal,
.s fichier source en assembleur, construit par le programmeur ou généré par le compilateur,
.sl bibliothèques de fichiers compilés et édités (édition de lien dynamique) en environnement HP,
.so identique au précédent dans l'environnement SYSTEM V R4.
Appel
La compilationex "compilation" d'un programme source C s'exécute en deux passes :
la première correspond à l'analyse syntaxiqueex "analyse syntaxique" qui affiche des messages d'erreurs si nécessaire,
la deuxième effectue la génération du code objet.
Il est possible de procéder à une troisième passe (optionnelle) d'optimisation du code.
La commande /bin/ccex "/bin/cc" a pour rôle :
d'analyser la liste des arguments de la commande,
de lancer puis d'enchaîner, selon les options de compilation choisies, un ou plusieurs des modules suivants :
cpp le préprocesseurex "préprocesseur", générateur du fichier suffixé par.i,
cc le compilateurex "compilateur", générateur du fichier objet suffixé par.o,
c2 l'optimiseur de codeex "optimiseur de code", générant un fichier exécutable plus compact et plus rapide à l'exécution,
as l'assembleurex "assembleur", générateur du fichier suffixé par.o,
ld l'éditeur de lienex "éditeur de lien", générateur du fichier exécutable a.outex "a.out" par défaut.
Dans tout ce qui suit, il faut distinguer le fichier objetex "fichier objet", suffixé par.o, généré par le compilateur, et le fichier exécutableex "fichier exécutable", généré par l'éditeur de lien. Les deux termes sont quelquefois abusivement confondus.
Quelques options de compilation
L'appel sans option du compilateur cc provoque l'exécution successive des différentes phases nécessaires à la génération du fichier exécutable qui sont enchaînées automatiquement.
Synopsis : cc [-option] [-option argument] fichier1 fichier2..fichiern
Quelques options de compilationex "options de compilation"
-c
génération d'un fichier objetex "fichier objet" (suffixés par.o) sans appel à l'éditeur de lien.
-D
définition dynamique d'une variable du préprocesseur, par exemple, l'option :
-Da=1
provoque la définition de la variable a = 1 à la compilation. Une modification de cette dernière nécessite une nouvelle compilation.
-E
appel du préprocesseur uniquement avec affichage des résultats sur la sortie standard.
-g
génération d'un fichier exécutable avec possibilité d'utilisation d'un débogueur tel dbxex "dbx". Il y a alors des informations permettant d'avoir l'équivalence entre la ligne du programme source C et les lignes d'assembleur.
-G
création d'un fichier partageable.
-I
recherche des fichiers en tête (header) dans le répertoire indiqué (par défaut le répertoire courant).
-lx
Recherche dans la bibliothèque spécifiée des références externes non satisfaites lors de l'appel de l'éditeur de lien
-o fichier_executable
génère le fichier exécutable de nom fichier_executable. Par défaut, c'est le fichier a.out.
-O (O majuscule)
Appel du module optimiseur de code. La compilation est alors plus longue.
-p
génération d'un code permettant le comptage du nombre d'appel à chaque fonction pour l'utilitaire profex "prof".
-P
Appel du préprocesseur cppex "cpp" seulement avec production d'un fichier suffixé par.i.
-qp
Idem -p
-S
Appel du compilateur seulement avec génération d'un fichier.s.
-v
exécution de la commande cc en mode verbeux.
Apports de la norme ANSI
A partir d'UNIX SYSTEM V R4, de nouvelles options de compilation sont définies :
Compilation en mode traditionnel (C Kernighan et Ritchie)
cc -Xt prog.c
Compilation en mode conforme (Norme ANSI et extensions locales)
cc -Xa prog.c
Compilation de programmes C strictement conformes à la norme ANSI
cc -Xc prog.c
Tout compilateur C conforme à la norme ANSI doit respecter au moins les normes de compilationex "normes de compilation" suivantes :
Nombre de blocs , de boucles (for, while, do), de tests (if, switch) imbriqués : 15
Nombre de directives du préprocesseur imbriquées (#if, #ifdef, #ifndef) : 8
Nombre de parenthèses pour les expressions : 31
Nombre de caractères significatifs pour une variable interne : 31
Nombre de caractères significatifs pour une variable externe : 6
Nombre d'identificateurs externes dans une unité de compilation : 511
Identificateur dans un bloc : 127
Identificateur de macroinstruction dans une unité de compilation : 1024
Nombre de paramètres d'une fonction et d'une macroinstruction : 31
Nombre de caractères sur une ligne logique ou dans une chaîne : 509
Nombre de directives #include imbriquées : 8
Nombre d'instructions case dans une instruction switch : 257
Nombre de champs d'une structure ou d'une union : 127
Nombre de structures ou d'union imbriquées : 15
Exemples : soient les fichier f1.c et f2.c contenant respectivement la fonction main et la fonction func. La commande
cc f1.c
provoquera, s'il y a une référence à la fonction func dans la fonction main, une recherche infructueuse à l'édition de lien, et la destruction des fichiers intermédiaires.
La commande
cc f1.c f2.c
aura l'effet suivant :
compilation des fichier f1.c et f2.c,
génération des fichiers f1.o et f2.o,
édition de lien et génération du fichier exécutable a.out.
La commande
cc f1.o f2.c
aura l'effet :
utilisation du fichier f1.o, sans recompilation du fichier f1.c,
compilation du fichier f2.c,
édition de lien et génération du fichier exécutable a.out.
Edition de lien
L'édition de lien suit la phase de compilation. C'est la dernière étape de génération du fichier exécutable.
Bibliothèques
Il est possible de se constituer des bibliothèqueex "bibliothèque"s de programmes que l'on peut appeler selon les besoins ce qui évite des compilations inutiles. Sous UNIX, les bibliothèques sont quelquefois appelées librairiesex "librairies", traduction imparfaite du mot anglais library.
L'éditeur de lien recherche dans les bibliothèques standardsEX "bibliothèque standard" ou spécifiques de programmes compilés et édités les références non satisfaites.
Bibliothèques standards
Les bibliothèques standards sont contenues dans les répertoires /lib ou /usr/lib. Elles sont préfixées par le mot clé lib.
Exemple
libm.a bibliothèque mathématique pour l'édition de lien statique,
libm.so bibliothèque mathématique pour l'édition de lien dynamique,
libcurses.a bibliothèque de gestion d'écran,
libtermlib.a bibliothèque de gestion des terminaux.
Edition de lien statique et édition de lien dynamique
L'édition de lien peut être statique ou dynamique.
Edition de lien statique ex "Edition de lien statique "
Les codes exécutables des références externes sont insérés dans le fichier objet à l'édition de lien. Une modification du code source d'une référence dans une bibliothèque nécessite alors la recompilation complète du programme.
Edition de lien dynamiqueex "Edition de lien dynamique"
Les références externes sont satisfaites soit au chargement soit à l'exécution ce qui permet d'obtenir des programmes plus compacts. De plus, une modification du code d'une référence dans une bibliothèque ne nécessite pas la recompilation du programme. En outre, plusieurs processus peuvent simultanément la partager. Il y a donc beaucoup d'avantages à l'utilisation de l'édition de lien dynamique. Le seul inconvénient est le léger ralentissement de l'exécution.
Usuellement, les bibliothèques utilisées pour l'édition de lien statique sont suffixées par.a, celles utilisées pour l'édition de lien dynamique par.so (SYSTEM V R4) ou.sl (HP-UX).
Appel
L'éditeur de lien est le programme /bin/ldex "/bin/ld". Il est appelé indirectement par le compilateur ou directement.
Synopsis : ld [-option] [-options_argument] fichier(s)
Options
-dn
désactive l'édition de lien dynamique, active par défaut.
-B[static] ou
-B[dynamic]
active l'édition de lien statique ou dynamique.
-lx
appel de l'éditeur de lien en faisant référence à une bibliothèque standard dont le nom complet est de la forme libx.a ou libx.so. Ainsi, si x = c la bibliothèque demandée est libc (bibliothèque c), si x = m la bibliothèque recherchée est libm (bibliothèque mathématique), etc.
Attention à l'ordre des bibliothèques appelées à la compilation. Comme en Fortran, il y a un ordre logique dans la recherche, dans ce cas de la gauche vers la droite.
-Lrépertoire
Recherche des bibliothèques dans le répertoire spécifié, différent des répertoires de recherche standards /libex "/lib" et /usr/libex "/usr/lib".
-m
affichage des fichiers utilisés.
-n
génération d'un code réentrant.
-N
Génération d'un code non partageable.
-s
suppression de la table des symboles du fichier exécutable.
Application : la commande cc appelle implicitement l'éditeur de lien.
Exemple
cc toto.c -o toto.dyn
cc toto.c -dn -o toto.sta
cc toto.c -Bstatic toto.sta
Opérations sur les bibliothèques
Archivage
Une cléex "clé" est une fonction d'une bibliothèque appelable depuis un programme externe. La commande /bin/arex "/bin/ar" permet de les gérer.
Synopsis : arEX "ar" option bibliothèque_archive clé1 clé2...clén
Options
c écrasement d'une clé existante, à utiliser avec l'option q
d destruction d'une clé,
m déplacement d'une clé à la fin de la bibliothèque,
q ajout d'une clé,
r remplacement d'une clé et ajout en fin de la bibliothèque,
t édition de la liste des clés,
x extraction d'une clé,
v mode verbeux.
Exemple : soit la bibliothèque lib.a, contenant dans l'ordre les fichiers a.o, b.o, c.o.
Pour remplacer le fichier b.o par le fichier d.o, on procède en trois étapes :
ar d lib.a b.o suppression de la clé b.o de la bibliothèque,
ar q lib.a d.o ajout de la clé d.o à la fin du fichier lib.a,
ar m lib.a c.o déplacement de la clé c.o à la fin du fichier lib.a.
La commande
ar t lib.a
édite la liste des clés constituant de la bibliothèque lib.a.
Le nombre de bibliothèques utilisables simultanément est illimité.
Réorganisation
L'utilitaire ranlibex "ranlib" convertit tout fichier au format archive en une forme pouvant être chargée plus rapidement par l'éditeur de lien. Il ajoute la table spécifique __SYMDEF au début de la bibliothèque ce qui est parfois nécessaire pour le bon déroulement de l'édition de lien, surtout après une modification de la bibliothèque.
Synopsis : ranlib [-t] fichier.a
L'utilitaire tsortex "tsort" permet de trier les clés d'une bibliothèque.
Synopsis : tsort fichier.a ou fichier.so
Exemples
Pour calculer le volume d'une sphere, on définit les fichiers sphere3.c ,vol.c, surf.c, circonf.c, pi.h comme suit :
/* fichier sphere3.c */
#include
int main(void)
{float r;
extern float volume(float);
printf("saisie du rayon : ");
scanf("%f",&r);
printf("rayon = %6.4f",r);
printf("volume de la sphere = %6.4f\n",volume(r));
return(1);
}
_
/* fichier vol.c : volume d'une sphère */
float volume(float r)
{float v;
extern float surface(float);
v = (r*surface(r))/3.;
printf(" v= %6.2f r = %6.2f\n", v,r);
return(v);
}
/* fichier surf.c : surface d'une sphère */
float surface(float r)
{extern float circonf(float);
return((float) 2*r*circonf(r));
}
/* fichier circonf.c : circonférence d'un cercle */
#include "pi.h"
float circonf(float r)
{ return((float) 2*PI*r);}
/* fichier pi.h */
#define PI 3.1416
Soient les bibliothèques lib2.a et lib3.a telles que l'exécution de la commande ar indique :
ar t lib2.a
surf.o
ar t lib3.a
circonf.o
La commande :
cc sphere3.c -o sphere3
a pour effet :
sphere3.c
undefined first referenced
symbol in file
volume sphere3.o
ld fatal: Symbol referencing errors. No output written to sphere3
*** Error code 13
Stop.
La commande :
cc sphere3.o vol.o -o sphere3
a pour effet :
undefined first referenced
symbol in file
surface vol.o
ld fatal: Symbol referencing errors. No output written to a.out
Les commandes :
cc sphere3.o vol.o surf.o circonf.o -o sphere3
cc sphere3.o vol.o -L/home/philipp/biblio -l2 -l3 -o sphere3
s'exécutent correctement.
La commande :
cc sphere3.o -L/home/philipp/biblio -l2 -l3 -o sphere3
ne s'exécute pas.
Analyse des programmes
Structure d'un fichier exécutable
Un fichier exécutable résulte de la compilation d'un programme source suivie de l'édition de lien. Son image sur disque est statique et se compose de quatre parties :
le premier en tête décrit le nombre de sections du fichiers, l'adresse de départ de l'exécution du processus, le nombre magiqueex "nombre magique" (magic numberex "magic number") indiquant le format du fichier exécutable (interprété, ou compilé),
l'en-tête de chaque section décrit sa taille et son adresse virtuelle à l'exécution,
les sections de données, chargées au début de l'exécution du processus, contenant les données initialisées, le texte du code exécutable, la pile et les données non initialisées contenues dans le bloc BSS (Block Start Symbol),
les sections contenant la table des symboles.
Cette structure est définie ci-dessous :
struct EXEC {
long a_magic; /* nombre magique (magic number) */
unsigned long a_text; /* taille du text segment */
unsigned long a_data; /* taille des données statiques initialisées */
unsigned long a_bss; /* taille des données dynamiques */
unsigned long a_syms; /* taille de la table des symboles */
unsigned a_txbase; /* base du text segment */
};
#define NMAGIC 04100 /* text segment en lecture seulement */
Formats des fichiers exécutables
Il existe plusieurs formats de fichiers exécutables : COFF, GOFF, XCOFF. La version UNIX SYSTEM V R4 propose la définition du format ELFex "format ELF" (Executing and Linking Format) pour les différents type de modules binaires :
les fichiers relogeablesex "fichiers relogeables" générés par le compilateur sont suffixés par.o,
les fichiers exécutablesex "fichiers exécutables" générés suite à l'édition de lien,
les fichiers partageablesex "fichiers partageables", suffixés par.so (Shared Objects) pour l'édition de lien dynamique.
Commandes associées
asex "as"
Ce programme génére le fichier objet associé à un fichier source écrit en assembleur suffixé par.s
Synopsis : as [option(s)] fichier_source
ctraceex "ctrace"
Ce programme crée, à partir d'un fichier source en C, un autre fichier source en C permettant d'exécuter le programme d'origine instruction par instruction. Il insère en outre des instructions d'impression de toutes les variables.
Synopsis : ctrace [option(s)] fichier_source > fichier_résultat
Les principales options sont :
f fonction_a_tracer
v fonction_a_ne_pas-tracer.
La redirection du fichier résultat est nécessaire car la syntaxe de la commande cc ne permet pas la redirection de l'entrée standard.
disex "dis"
Cet utilitaire permet de désassembler des fichiers objets, au format COFF, ou des fichiers au format archive. Il produit un fichier dans un langage d'assemblage.
Synopsis : dis fichier_objet ou dis fichier_executable
dumpex "dump"
Edition des fichiers exécutables au format COFF.
Synopsis : dump [option(s)] fichier_executable
Les options à utiliser sont abcfghLlrstv.
lorderex "lorder"
A partir d'une liste de fichiers objets ou archives, cet utilitaire en génère un ensemble de couple, dont le premier renvoie aux références externes du second.
Synopsis : lorder fichier(s)
mcsex "mcs"
Cet utilitaire permet de modifier directement la section.comment d'un fichier objet.
Synopsis : mcs fichier_objet
nm
La table des symboles associée à un fichier objet ou exécutable est obtenue par l'appel de la commande nm, dans le cas où le fichier n'a pas été réduit suite à l'exécution de la commande stripex "strip".
Synopsis : nmex "nm" [-option(s)] fichier_objet ou fichier_executable
Son effet est d'indiquer le nom des fonctions et leurs variables dans la bibliothèque concernée.
Principales options
BSD
-a impression de tous les symboles,
-g impression des variables externes seules,
-n impression des variables triées,
-u impression des variables indéfinies.
SYSTEME V
-o impression des valeurs en octal,
-x impression des valeurs en hexadécimal,
-v tri des symboles externes par valeur avant impression,
-e impression des variables statiques et externes seulement,
-u impression des variables indéfinies.
Exemple : nm f.o
Type Liste de variables
T(texte) f1
D(données) f3
U(undefined) f2
Les adresses indiquées sont relatives au début du programme.
Symbols from sphere3.o:
Name Value Class Type Size Line Section
sphere3.c | | file | | | |
DGROUP | 0| static| | | |.data
scanf | 0| extern| | | |
printf | 0| extern| | | |
main | 0| extern| | | |.text
volume | 0| extern| | | |
_fltused | 0| extern| | | |
size
Le programme sizeex "size" exécuté sur un fichier objet exécutable affiche sa taille ainsi que celle de chacune de ses sections.
Synopsis : size fichier_executable
strip
La commande shell stripex "strip" permet de supprimer les sections contenant la table des symboles d'un fichier exécutable donc d'en réduire la taille et d'empêcher tout désassemblage ultérieur.
Synopsis : strip fichier_executable
truss
La commande trussex "truss" est disponible sous UNIX SYSTEM V. Elle permet d'obtenir la liste des appels systèmes utilisés et des signaux reçus par une commande.
Synopsis : truss [-t[!]appel_système][-s[!] signal...] commande
Les arguments ainsi que la valeur de retour de l'appel système indiqué après l'argument sont affichés sauf s'il est précédé du caractère !.
Le signal dont le numéro est indiqué après l'argument s est suivi au cours de son exécution sauf s'il est précédé du caractère !.
Exemple
truss -t open -t close -s 2 ls -l
Dépendances
cscopeex "cscope"
Ce programme interactif localise des éléments de code spécifiques, permet donc d'effectuer des recherches et éventuellement de modifier directement des fichiers sources d'un des formats C, lex, yacc. Il construit aussi une table des références croisées lui permettant de mettre en correspondance les noms des variables ainsi les fichiers utilisés. Il est ensuite possible de faire des modifications sur des variables données.
Synopsis : cscope fichier.c
cflowex "cflow"
Cet utilitaire est un outil de vérification des dépendances des fonctions. Il génère un tableau de références externes aux fonctions dans des fichiers d'un des formats C, lex, yacc ainsi que dans des modules assembleurs.
Synopsis : cflow [option(s)] fichier(s)
Le résultat est constitué d'un ensemble de lignes numérotés, chacune contenant :
son numéro, utilisé pour accéder à l'objet,
le nom d'un objet,
le séparateur : suivi de la définition de l'objet à savoir le triplet
(type, nom du fichier source, numéro de la ligne contenant la définition).
Exemple : soit le fichier ex_prof.c
#define N 50
unsigned long prod(unsigned int, unsigned);
unsigned long fact(unsigned int);
void bouc(void);
int main(void)
{int i;
long y=0l;
bouc();
for(i=1;i trace.c appel de ctrace sur le fichier source et redirection
cc trace.c compilation du fichier trace.c
a.out exécution "pas à pas"
On constate que la variable nl est incrémentée à chaque itération de la boucle while donc à chaque caractère. En effet, le test de comparaison du caractère lu à NewLine est faux car en C, l'opérateur de comparaison est == et non = qui est l'opérateur d'affectation.
17°) Corriger chaineCl.c avec l'éditeur de votre choix (remplacer c='\n' par c=='\n', ), le recompiler (cc -o chn chaineCl.c) puis l'exécuter (chn). Cette fois-ci, le résultat est correct.
On peut aussi exécuter chn avec une redirection. Ainsi, la commande chn }
??- ~
Nouveaux types
Le type long floatex "long float" n'est plus un type synonyme pour le type double. Il est utilisé pour définir des nombres en quadruple précisionex "quadruple précision".
Le type size_tex "size_t" remplace le type int pour décrire un entier indéfiniex "entier indéfini". Il permet d'utiliser l'objet retourné par l'opérateur sizeof.
Le type void est incorporé au langage.
Le type void * est utilisé comme pointeur générique ou pointeur universel et remplace le type char * dans ce rôle. Ce type empêche la comparaison de pointeur sur des types différents. Il est nécessaire d'utiliser l'opérateur de transtypage dans ce cas.
Le type ptrdiff_tex "ptrdiff_t" indique le nombre d'objet représenté par une soustraction de pointeurs.
Le type wchar_tex "wchar_t" permet d'encoder un wide character.
Le type div_tex "div_t" est la structure retournée par la fonction divex "div".
Le type fpos_tex "fpos_t" est la position courante dans de grands fichiers.
Le type jump_bufex "jump_buf" est un tableau static utilisé par longjumpex "longjump".
Le type lconvex "lconv" est retourné par la fonction localeconvex "localeconv".
Le type va_listex "va_list" est un pointeur utilisé par la fonction va_endex "va_end".
La norme définit les suffixes F, L, U pour les constantes de type float, long et unsigned.
Les qualificatifs const, noalias, volatiles permettent de se protéger des optimisations automatiques du compilateur.
Préprocesseur
Les nouvelles directives #elif et #pragma, les opérateurs ##, le mot clé defined, ont été introduits pour augmenter sa puissance.
En outre, le préprocesseur contient cinq macro prédéfinies :
__LINE__ex "__LINE__" /* la ligne courante dans le fichier source */
__DATE__ex "__DATE__" /* la date de la compilation */
__FILE__ex "__FILE__" /* le nom du fichier source */
__TIME__ex "__TIME__" /* l'heure de compilation */
__STDC__ex "__STDC__" /* conformité du compilateur à la norme ANSI */
Aucune de ces macro-définitions n'est modifiable par les directives #define et #undef.
En outre, la nouvelle directive definedex "defined", utilisée en conjonction avec la directive #if permet d'écrire des instructions complexes.
Heure universelle
La norme ANSI remplace l'heure de Greenwitch avec l'Heure Universelle Coordonnée (Universal Time Coordinated).
Structure
Il est possible de transmettre par valeur ou par adresse une structure à une fonction, d'affecter directement une structure à une autre structure.
Annexe 3 : Rappels sur la représentation de l'information
Si le système décimal est le système de numération utilisé usuellement par les êtres humains, le système binaire est le système de numération naturel d'une ordinateur dans la mesure où l'information digitale représente exactement la présence ou l'absence d'un signal electrique.
Un ensemble de n bits permettant la représentation de 2n états, le codage ex "codage "consiste à établir une loi de correspondance (appelée code) entre les informations à représenter et les configurations binaires possibles de telle sorte qu'à chaque opération corresponde une et une seule configuration binaire.
La conversion d'un système de codage en un autre est appelée transcodageex "transcodage".
Interprétation selon le type de l'objet
Bien que reposant tous sur le système binaire, les différents types de codage utilisés dans un ordinateur dépendent de l'entité à coder. Une même suite binaire peut avoir de multiples interprétations. Ceci explique que l'opération de lecture du contenu de la mémoire d'un ordinateur ne peut être faite sans la connaissance de l'adresse et du type de l'information à laquelle on désire accéder.
Représentation binaire et hexadécimale
En système binaire, les informations sont formées de suites "assez longues" de 0 et de 1. Si l'utilisateur dialogue sous cette forme avec l'ordinateur, la longueur de la chaîne de bits du codage freinent considérablement la compréhension du dialogue. Les risques d'erreurs sont nombreux. Le système de base 16 (hexadécimal), permet une représentation condensée et claire de 4 bits. Comme il est nécessaire de disposer de 16 symboles; on utilise les chiffres 0 à 9 et les lettres A, B, C, D, E F. La correspondance s'effectue suivant le tableau suivant :
hexadécimal binaire hexadécimal binaire
0 0000 8 1000
1 0001 9 1001
2 0010 A 1010
3 0011 B 1011
4 0100 C 1100
5 0101 D 1101
6 0110 E 1110
7 0111 F 1111
Dans ce système, un octet est représenté par deux caractères hexadécimaux; par exemple l'octet
1011 0010 s'écrira B2.
Il existe aussi des représentations en système octal. Dans ce cas, seuls les chiffres de 0 à 7 sont utilisés et 3 bits suffisent à leur représentation. Les deux premières colonnes du table au précédent fournissent la correspondance.
Nous allons maintenant étudier cas par cas les divers types de codages utilisés par un ordinateur en distingant les données et les instructions.
Données alphanumériques
On appelle donnée alphanumériqueex "donnée alphanumérique", (par opposition à donnée numériqueex "donnée numérique") toute donnée qui ne peut donner lieu à un calcul arithmétique. Dans la pratique une chaîne de caractères est composée des caractères disponibles sur le clavier du terminal.
lettres majuscules et minuscules,
chiffres 0, 1,... 9,
symboles ! ? ; : , . < > = etc.,
caractères spéciaux : retourchariot, saut de ligne, saut de page, etc.
Tout caractère est codé sur 1 octet à partir d'une table de codage. On en distingue essentiellemment deux : la table EBCDIC ex "EBCDIC "et la table ASCIIex "ASCII". Le code EBCDICEX "EBCDIC" (Extended Binary Coded Decimal Interchange CodeEX "Extended Binary Coded Decimal Interchange Code"), que l'on trouve principale ment sur les gros systèmes IBM utilise un octet par caractère et permet donc d'en coder 256.
Dans certains cas, on utilise moins de 256 symboles. Le code ASCII (American Standard Code for Information InterchangeEX "American Standard Code for Information Interchange"), utilisé sur les micro et sur les minisordinateurs, utilisait à l'origine 7 bits. Le 8ème bit peut être utilisé comme bit de contrôle ex "bit de contrôle " pour vérifier la validité des transferts de données. Il est aussi utilisé pour étendre le jeu de caractères de base avec des caractères semigraphiques, dont le code ASCII est compris entre 128 et 255 (Cf table cidessous).
33 !
34 "
35 #
36 $
37 %
38 &
39 '
40 (
41 )
42 *
43 +
44 ,
45
46 .
47 /
48 0
49 1
50 2
51 3
52 4
53 5
54 6
55 7
56 8
57 9
58 :
59 ;
60 <
61 =
62 >
63 ?
64 @
65 A
66 B
67 C
68 D
69 E
70 F
71 G
72 H
73 I
74 J
75 K
76 L
77 M
78 N
79 O
80 P
81 Q
82 R
83 S
84 T
85 U
86 V
87 W
88 X
89 Y
90 Z
91 [
92 \
93 ]
94 ^
95 _
96 `
97 a
98 b
99 c
100 d
101 e
102 f
103 g
104 h
105 i
106 j
107 k
108 l
109 m
110 n
111 o
112 p
113 q
114 r
115 s
116 t
117 u
118 v
119 w
120 x
121 y
122 z
123 {
124 |
125 }
126 ~
127
128
129
130
131
132
133
134
135
136 Æ
137 0
138 `
139 9
140 R
141
142
143
144
145
146
147
148
149 "
150
151
152 Ü
153 "!
154 a
155 :
156 S
157
158
159 x
160
161 ¡
162 ¢
163 £
164 ¤
165 ¥
166 ¦
167 §
168 ¨
169 ©
170 ª
171 «
172 ¬
173
174 ®
175 ¯
176 °
177 ±
178 ²
179 ³
180 ´
181 µ
182 ¶
183 ·
184 ¸
185 ¹
186 º
187 »
188 ¼
189 ½
190 ¾
191 ¿
192 À
193 Á
194 Â
195 Ã
196 Ä
197 Å
198 Æ
199 Ç
200 È
201 É
202 Ê
203 Ë
204 Ì
205 Í
206 Î
207 Ï
208 Ð
209 Ñ
210 Ò
211 Ó
212 Ô
213 Õ
214 Ö
215 ×
216 Ø
217 Ù
218 Ú
219 Û
220 Ü
221 Ý
222 Þ
223 ß
224 à
225 á
226 â
227 ã
228 ä
229 å
230 æ
231 ç
232 è
233 é
234 ê
235 ë
236 ì
237 í
238 î
239 ï
240 ð
241 ñ
242 ò
243 ó
244 ô
245 õ
246 ö
247 ÷
248 ø
249 ù
250 ú
251 û
252 ü
253 ý
254 þ
255 ÿD'autre part, comme il existe de nombreux alphabets (russe, grec, etc.), il n'y a pas unicité de la représentation. C'est pourquoi il existe un code ascii anglais, allemand, français, etc. Ceci pose de nombreux problèmes pour l'édition. Une norme "universelle " est actuellement à l'étude.
Le code ASCII international est le code cidessus qui s'interprète de la façon suivante :
Les caractères de contrôle ont un code compris entre 0 et 32.
Les caractères usuels ont un code compris entre 33 et 127.
Les autres caractères (étrangers ou semigraphiques) ont un code compris entre 128 et 256.
Le lecteur vérifiera que dans ce système de codage, la chaîne JEAN est codée en hexadécimal 4A45414E et que la chaîne 12 est codée 3132.
Dans le cas d'un nombre codé en chaîne de caractères, il est impossible d'effectuer sur celuici des opérations arithmétiques à moins de l'avoir préalablement converti en donnée numérique.
Nombres entiers
Pour représenter les nombres relatifs, il y a deux ex ""codages :
les nombres entiers non signés,
les nombres entiers signés.
Nombres entiers non signés
On appelle nombre entier non signé ex "nombre entier non signé "tout entier naturel codé sans signe. Pour un codage sur n bits numérotés de droite à gauche et de 0 à n1; le nombre entier non signé p s'écrit sous la forme binaire :
p = INCORPORER Equation
où CARSPECIAUX 100 \f "Symbol"i représente la valeur du bit de position i. D'après l'algorithme d'Euclide, les CARSPECIAUX 100 \f "Symbol"i sont les restes des divisions successives par 2.
Exemple
13 s'écrit sur un octet 0000 1101.
Les bits de droite sont les bits de poids faible ex "bits de poids faible " et les bits de gauche les bits de poids fortex "bits de poids fort".
Nombres entiers signés
On appelle nombre entier signé ex "nombre entier signé "tout entier relatif. On utilise le nième bit à partir de la droite dit bit le plus significatifex "bit le plus signicatif" (most significant bitex "most significant bit") pour coder le signe. Quand ce bit est nul, le nombre est positif. Quand il est égal à 1, le nombre est négatif.
Dans le cas d'un nombre positif ou nul, on utilise les n1 bits restants pour écrire sa valeur absolue suivant le principe des entiers non signés.
Ainsi sur un octet
+ 13 s'écrit 00001101
0 s'écrit 00000000
+ 127 s'écrit 01111111
Dans le cas d'un nombre négatif, on utilise les n1 bits restants non pas pour écrire la partie positive du nombre mais son complément dont il existe deux définitions.
Le complément à 1 ex "complément à 1 "d'un nombre A codé sur n bits, noté C1(A) vérifie :
C1(A) + A = 2n 1
Le complément à 2 ex "complément à 2 "d'un nombre A codé sur n bits, noté C2(A) vérifie :
C2(A) + A = 2n
Le lecteur vérifiera alors que le complément à 1 d'un nombre correspond à la négation bit à bit de ce nombre et que
C2(A) = C1(A) + 1 = C1(A1)
Tout constructeur utilise une des deux représentations sur une machine donnée. Ainsi pour écrire 3 sur un octet :
on code 2 sur 7 bits 0000010
on prend le complément à 1 1111101
et on ajoute le bit de signe 11111101
De la même façon :
1 s'écrit 11111111
128 s'écrit 10000000
11 s'écrit 11110101
Exemple
Représentation sur 32 bits du nombre + 1973
En numération :
binaire 1973 = 00 0111 1011 0101
hexadécimale 1973 = 0 0 0 0 0 7 B 5
En conclusion, le lecteur vérifiera que sur n bits, il est possible de stocker des nombres entiers signés m tels que :
INCORPORER Equation
Nombres réels
Les nombres réels sont composés des nombres rationnels et des nombres irrationnels (par exemple 2). Seuls sont représentables exactement les nombres rationnels. Les nombres irrationnels sont approximés et l'erreur de représentation est appelée erreur de troncatureex "erreur de troncature".
On distingue trois types de représentation des nombres réels :
les nombres réels DCBex "réels DCB",
les nombres réels représentés avec une virgule flottante ou réels flottantsex "réels flottants",
les nombres ex "réels décimaux fixes" réels représentés avec une virgule dont la position est fixe ou réels fixesex "réels fixes",
Ces représentation peuvent être liées à une machine ou un logiciel ce qui peut provoquer des problèmes de portabilité.
Cas des nombres réels flottants
Dans cette représentation, le nombre est décomposé en deux parties :
l'exposant e qui est un entier signé,
la mantisse M.
La valeur d'un nombre x ainsi représenté est par construction
x = S M bc
avec :
S le signe du nombre,
M la mantisse,
b la base de numération (en général 2 ou 16),
c la caractéristique, sur p bits, liée à l'exposant e par la relation
c = e + 2p-1
Il faut représenter le signe, la mantisse, l'exposant.
INCORPORER Designer \s \* fusionformat
Chaque constructeur pouvant utiliser des longueurs de mot, de caractéristique, ou d'exposant différentes, il n'y a pas unicité de la représentation ce qui peut conduire à des résultats différents pour un calcul donné. Nous choisissons ici des mots de 32 bits, une caractéristique sur 7 bits et une mantisse sur 24 bits. Nous présentons en outre la norme IEEE 754 des nombres flottants qui utilise une caractéristique sur 8 bits et une mantisse sur 23 bits pour les nombres en simple précisionex "simple précision", une caractéristique sur 11 bits et une mantisse sur 53 bits pour les nombres en double précisionex "double précision".
Signe
Le bit de gauche est le bit de signe. Par convention, il vaut :
0 si le nombre est positif
1 si le nombre est négatif
Caractéristique
Elle est représentée sur les p bits suivants. Puisque
0 CARSPECIAUX 163 \f "Symbol" c CARSPECIAUX 163 \f "Symbol" 2p
on a :
2p-1 CARSPECIAUX 163 \f "Symbol" e CARSPECIAUX 163 \f "Symbol"2p-1 - 1
Quand p = 7 ou 8 (norme IEEE 754), on peut représenter des exposants positifs et négatifs d'une valeur absolue "raisonnable".
Mantisse
La mantisse indique le nombre de chiffres de la représentation. Avec des mots de 32 bits, elle est représentée sur (32 p 1) bits soit 24 ou 23 bits (norme IEEE 754). Le premier problème est le choix de la représentation de la mantisse. En effet, on peut écrire le nombre suivant de différentes manières :
1973 = 197,3 * 10 = 19,73 * 102 = 0,1973 *104
Dans la dernière égalité, la mantisse vaut 0,1973 et l'exposant 4.
La forme normaliséeex "forme normalisée" de la mantisse est définie par la relation :
1/b CARSPECIAUX 163 \f "Symbol" M < 1 (1)
Elle permet d'obtenir le maximum de chiffres significatifs.
Exemple
En base 10, le nombre 1,973 a pour mantisse normalisée 0,1973 et pour caractéristique 65 (1+64).
Dans la pratique, on ne représente que la partie fractionnaire de la mantisse. Pour 1,973, on ne codera que 1973.
Calcul de la mantisse
Chacun des bits de la mantisse s'il vaut 1 représente 2-i, i étant sa position. La mantisse vaut par définition
INCORPORER Equation
où CARSPECIAUX 100 \f "Symbol"i est la valeur 0 ou 1 de son ième bit.
Si tous les CARSPECIAUX 100 \f "Symbol"i valent 1, alors
INCORPORER Equation
Exemple 1
Le nombre réel +1973 s'écrit :
1973 = 7B5 = 0,7B5 * 163
donc :
M = 7B5
c = 2p-1 + 3 = 26 + 3 = 67 = 43 (en base 16)
S = 0
en hexadécimal : 1973 = 4 3 7 B 5 000
codé en binaire : 1973 = 0100 0011 0111 1011 0101 0...0
Avec la norme IEEE 754, on obtient :
M = 7B5
c = 2p-1 + 3 = 27 + 3 = 131 = 83 (en base 16)
S = 0
en binaire 1973 = 0100 0001 1011 1101 1010 10...0
en hexadécimal 1973 = 4 1 B D A 8 0 0
Exemple 2
Le nombre réel 1973 s'écrit avec le complément à 2 du nombre positif soit :
1973 = BC84B000
Avec la norme IEEE 754, on obtient :
1973 = BD425800
Choix de la base de représentation
Plusieurs bases sont possibles. Le choix final est effectué en fonction de la précision des calculs, de la précision et de la valeur absolue de la représentation obtenue.
Remarque 1
Pour additionner deux nombres réels flottants, on commence par réduire les deux nombres au même exposant.
Si b vaut 2, on ne change pas la valeur du nombre en décalant la mantisse d'une position vers la droite et en augmentant l'exposant de 1.
Dans le cas où b vaut 16, il faut décaler la mantisse de quatre positions et augmenter l'exposant de 1 pour ne pas changer la valeur du nombre. Mais lors du décalage d'une position vers la droite, le bit n de la mantisse est perdu ce qui provoque une perte de précision.
Remarque 2
Si b vaut 2, l'inéquation (1) devient :
1/2 CARSPECIAUX 163 \f "Symbol" M < 1
Le premier bit de la mantisse M est toujours égal à 1 pour tout x non nul et cette représentation assure le nombre maximum de bits non nuls.
Si b vaut 16, l'inéquation (1) devient :
1/16 CARSPECIAUX 163 \f "Symbol" M < 1
4 bits sont nécessaires pour représenter les différentes valeurs possibles 1/16, 2/16,...15/16 du premier chiffre de la mantisse. Le lecteur vérifiera que tout nombre compris entre 1/16 et 1/8 a une mantisse dont les 3 premiers bits sont nuls et que dans ce cas, on perd trois bits significatifs.
Remarque 3
Pour un mot en base b, avec une mantisse sur n bits et une caractéristique sur p bits, le plus grand nombre positif représentable mb est :
INCORPORER Equation
Ce nombre dépend de la base choisie et est indépendant du nombre de bits de la mantisse.
En base 16 et 2 et avec p = 7, on obtient :
m16 = 1663 CARSPECIAUX 187 \f "Symbol" 1075
m2 = 263 CARSPECIAUX 187 \f "Symbol" 1019
En base 16 et 2 et avec p = 8, (norme IEEE 754), on obtient :
m16 = 16127 CARSPECIAUX 187 \f "Symbol" 10151
m2 = 2127 CARSPECIAUX 187 \f "Symbol" 1038
Le choix final de la base 16 est donc un compromis entre la grandeur de la valeur absolue et la précision.
Recherche du nombre de bits de la mantisse
Soient :
x = sbn M et y = sbn M'
alors, y devient négligeable devant x dès que l'équation :
x + y = x
est vérifiée. Nous avons vu que la mantisse M représente la suite des puissances négatives de 2. On va utiliser ces propriétés et calculer le nombre n de bits de la mantisse par l'algorithme suivant :
Début
n : = 0;
y : = 2-n ;
x : = 1;
boucle
si x+y = x alors imprimer n1; exit ;
sinon n = n+1; y : = 2-n ;
is
aller_à boucle
fin
Calcul du nombre de chiffres significatifs
Soit y = 2-n le plus petit nombre représentable sur la machine considérée. On cherche p tel que :
2-n = 10-p
d'où on tire la relation :
p = n log2 CARSPECIAUX 187 \f "Symbol" 0.3 n
Une mantisse de n bits représente p chiffres significatifs d'où une mantisse sur 23 bits (norme IEEE 754 en simple précision) donne 6 chiffres significatifs et une mantisse sur 53 bits (norme IEEE 754 en double précision) donne 15 chiffres significatifs.
Instructions
Le codage d'une instruction machine est lié à son architecture interne. Il est variable suivant la longueur des mots et le nombre d'adresses. Le code opération dépend du nombre d'instructions possibles.
A chacun des éléments d'information composant l'instruction est associée une zone de plusieurs bits pour coder les différents états possibles de cette information. Par exemple, 6 bits pour le code instruction permettent de coder 26 instructions, 4 bits pour l'adresse du premier opérande permettent un choix de 16 registres, et 16 bits d'adresse mémoire peuvent adresser 216 mots.
Images
Une image d'un moniteur graphique est un en ensemble de lignes composées de point élémentaires appelés pixelsex "pixels" (picture elementex "picture element") caractérisé par leur teinte appelée niveau de gris pour les images monochromes et couleur pour les images en couleur. Chaque pixel d'un moniteur graphique courant est souvent représenté sur un octet et peut avoir 256 niveaux de gris. Dans le cas d'images en couleur, chaque pixel est coloré à partir des couleurs de base rouge, vert, bleu, chacune pouvant être représentée sur un octet. Dans ce cas, on a une panoplie d'environ deux millions de couleurs. Les définitions actuelles varient entre 340*200 et 1200*1000 pixels.
Corrigé des exercices
Chapitre 1
Exercice 1.1.
#include
int main(void)
{int c,c1; c = c1 = 0;
while (( c = getchar()) != EOF )
/* test pour introduire les espaces séparateurs de mots, */
/* c1 mémorise le caractère de la saisie précédente */
{if (c1 ==' ' && c != ' ') printf(" ");
c1 = c;
if (c != ' ' && c != '\n' ) printf("%c",c);
}
}
// le fichier donnée est le fichier source
#include int main(void) {int c,c1; c = c1 = 0;while (( c = getchar()) != EOF )/* test pour introduire les blancs séparateurs de mots *//* c1 conserve le caractère de la saisie précédente */ { if (c1 ==' ' && c != ' ') printf(" "); c1 = c;if (c != '' && c != '\n') printf("%c",c); }}
Exercice 1.2
#include
int main(void)
{int c ;
while (( c = getchar()) != EOF )
{if ( c == '\t') {printf("-");printf("\b>\n");}
if ( c == '\b') {printf("entier");
++pt ->entier;/* incrémentation du champ entier */
imprime(tableau,pt);
printf("\neffet de i=(++pt) ->entier");
i=(++pt)->entier; /* post-incrémentation du pointeur pt */
imprime(tableau,pt);
printf("\ti= %d\n",i);
printf("\neffet de ++pt ->flottant"); /* incrémentation du champ flottant */
++pt ->flottant;
imprime(tableau,pt);
i=(pt++)->entier; /* post-incrémentation du pointeur pt */
printf("\neffet de i=(pt++) ->entier");
imprime(tableau,pt);
printf("\ti= %d\n",i);
i=pt++->entier; /* post-incrémentation du pointeur pt */
printf("\neffet de i=pt++->entier");
imprime(tableau,pt);
printf("\ti= %d\n",i);
printf("\n\t*pp = %x",*pp);
printf("\t(*pp)->flottant = %3.1f\n",(*pp)->flottant);
i=(*pp)->entier++; /* post-incrémentation du champ entier */
printf("\neffet de i=(*pp)->entier++");
imprime(tableau,*pp);
printf("\ti= %d\n",i);
i=(*pp)++->entier; /* post-incrémentation du pointeur *pp */
printf("\neffet de i=(*pp)++->entier");
imprime(tableau,*pp);
printf("\ti= %d\n",i);
i=(*pp++)->entier; /* post-incrémentation du pointeur pp */
printf("\neffet de i=(*pp++)->entier");
imprime(tableau,*pp);
printf("\ti= %d\n",i);
}
void modifRvaleur(struct intfloat *pt, struct intfloat tableau[])
{void imprime(struct intfloat [], struct intfloat *);
int i = 32;
struct intfloat **pp = &pt;
printf("\nappel de modifRvaleur");
printf("\n\tpp = %x\t*pp = %x",pp, *pp);
printf("\neffet de (++pt) ->entier =i");
(++pt)->entier=i; /* pré-incrémentation du pointeur pt */
imprime(tableau,pt);
(pt++)->entier =i; /* post-incrémentation du pointeur pt */
printf("\neffet de (pt++) ->entier =i");
imprime(tableau,pt);
pt++->entier =i; /* post-incrémentation du pointeur pt */
printf("\neffet de pt++->entier=i");
imprime(tableau,pt);
/* (*pp)->entier++=i; L-valeur obligatoire */
(*pp)++->entier=i; /* post-incrémentation du pointeur *pp */
printf("\neffet de (*pp)++->entier=i");
imprime(tableau,*pp);
(*pp++)->entier =i; /* post-incrémentation du pointeur pp */
printf("\neffet de (*pp++)->entier=i");
imprime(tableau,*pp);
}
void imprime(struct intfloat tableau[], struct intfloat *p)
{int i;
printf("\n\tpt = %x\n",p);
for (i = 0 ; i < TAILLE; i++) printf("\t%d",tableau[i].entier);
printf("\n");
for (i = 0 ; i < TAILLE; i++) printf("\t%3.1f",tableau[i].flottant);
printf("\n");
}
// résultat
Valeurs d'initialisation
pt = ffd6
0 2 4 6 8
1.0 4.0 7.0 10.0 13.0
appel de modifLvaleur
pp = ffd2 *pp = ffd6
effet de ++pt ->entier
pt = ffd6
1 2 4 6 8
1.0 4.0 7.0 10.0 13.0
effet de i=(++pt) ->entier
pt = ffdc
1 2 4 6 8
1.0 4.0 7.0 10.0 13.0
i= 2
effet de ++pt ->flottant
pt = ffdc
1 2 4 6 8
1.0 5.0 7.0 10.0 13.0
effet de i=(pt++) ->entier
pt = ffe2
1 2 4 6 8
1.0 5.0 7.0 10.0 13.0
i= 2
effet de i=pt++->entier
pt = ffe8
1 2 4 6 8
1.0 5.0 7.0 10.0 13.0
i= 4
*pp = ffe8 (*pp)->flottant = 10.0
effet de i=(*pp)->entier++
pt = ffe8
1 2 4 7 8
1.0 5.0 7.0 10.0 13.0
i= 6
effet de i=(*pp)++->entier
pt = ffee
1 2 4 7 8
1.0 5.0 7.0 10.0 13.0
i= 7
effet de i=(*pp++)->entier
pt = ffd6
1 2 4 7 8
1.0 5.0 7.0 10.0 13.0
i= 8
appel de modifRvaleur
pp = ffd2 *pp = ffd6
effet de (++pt) ->entier =i
pt = ffdc
1 32 4 7 8
1.0 5.0 7.0 10.0 13.0
effet de (pt++) ->entier =i
pt = ffe2
1 32 4 7 8
1.0 5.0 7.0 10.0 13.0
effet de pt++->entier=i
pt = ffe8
1 32 32 7 8
1.0 5.0 7.0 10.0 13.0
effet de (*pp)++->entier=i
pt = ffee
1 32 32 32 8
1.0 5.0 7.0 10.0 13.0
effet de (*pp++)->entier=i
pt = ffd6
1 32 32 32 32
1.0 5.0 7.0 10.0 13.0
Exercice 6.3
#include
struct complexe { float x; float y;}; /* variable globale */
int main(void) /* norme ANSI */
{struct complexe z,z1,z2;
/* définitions */
/* prototypes */
void lecture(struct complexe *);
void imp(struct complexe);
void add(struct complexe, struct complexe, struct complexe *);
void mul(struct complexe, struct complexe, struct complexe *);
/* lecture et impression des données */
lecture(&z1); imp(z1);
lecture(&z2); imp(z2);
/* addition et impression du résultat */
add(z1,z2,&z); imp(z);
/* multiplication et impression du résultat */
mul(z1,z2,&z); imp(z);
}
void lecture(struct complexe *z)
{float x; /* variable auxiliaire pour la partie réelle */
printf(" saisie du nombre complexe :\n ");
printf(" partie réelle : ");
scanf("%f",&x); z->x=x; /* saisie directe pour la partie imaginaire */
printf(" partie imaginaire : ");
scanf("%f",&(*z).y);
}
void imp(struct complexe z)
/* impression du nombre complexe z */
{printf(" partie réelle : ");
printf("%6.2f\n",z.x);
printf(" partie imaginaire : ");
printf("%6.2f\n",z.y);
}
void add(struct complexe z1, struct complexe z2, struct complexe *z )
/* addition de deux nombres complexes z1, z2 */
/* résultat dans z */
{printf(" addition des nombres z1, z2\n");
z -> x = z1.x + z2.x;
z -> y = z1.y + z2.y;
}
void mul(struct complexe z1, struct complexe z2, struct complexe *z )
/* multiplication de deux nombres complexes */
/* z1, z2; résultat dans z */
{printf("multiplication des nombres z1, z2\n");
z -> x = z1.x * z2.x - z1.y * z2.y;
z -> y = z1.x * z2.y + z1.y * z2.x;
}
// résultat
saisie du nombre complexe :
partie réelle : 4.00
partie imaginaire : 8.00
saisie du nombre complexe :
partie réelle : 7.00
partie imaginaire : 4.00
addition des nombres z1, z2
partie réelle : 11.00
partie imaginaire : 12.00
multiplication des nombres z1, z2
partie réelle : -4.00
partie imaginaire : 72.00
Exercice 6.4
#include
#include
#define MAX 20 /* nombre maximum de noms */
#define ever (;;) /* boucle de saisie */
/* structures de portée globales */
struct pers {char nom [20 ]; struct pers *tsuc;};
typedef struct {char nom[20];} name;
void main(void)
{int i,k;
struct pers liste [MAX ];
/* prototypes */
void saisie(void); /* saisie des noms */
void tri(struct pers liste [ ]); /* tri avec les pointeurs */
int lire(struct pers liste[]); /* lecture des donnees */
saisie(); /* saisie de la liste dans un fichier */
k=lire(liste); /* constitution des pointeurs des successeurs */
printf("Liste non triée\n\n");
printf("Numéro Adresse Nom Adresse du successeur\n");
for(i=0;inom, liste [i ].nom))
{pred = j; j = j->tsuc;}
/* modification du champ tsuc */
liste [i ].tsuc = j;
pred->tsuc = &liste [i ];
i++; /* enregistrement suivant */
}
fclose(fp);
return(i);
}
int st(char *s, char *t) /* comparaison de chaîne de caractères */
{int ret;
ret = strcmp(s,t);
if (ret == 0) return(-1);
if (retnom); j = j->tsuc;}
}
// résultat
Liste non triée
Numéro Adresse Nom Adresse du successeur
0 fe24 feea
1 fe3a JACQUES febe
2 fe50 ROMAIN fe24
3 fe66 CLAUDE fea8
4 fe7c MARIE-SOL fed4
5 fe92 PIERRE fe50
6 fea8 CLAUDE fe3a
7 febe JULIE fe7c
8 fed4 NOEL fe92
9 feea CAROLINE fe66
tri par ordre alphabétique
CAROLINE
CLAUDE
CLAUDE
JACQUES
JULIE
MARIE-SOL
NOEL
PIERRE
ROMAIN
Chapitre 8
Exercice 8.1
#include
int main(int argc,char *argv[])
/* version de la commande UNIX cat */
/* recopie du (des) fichier (s) d'entrée sur la sortie standard */
/* récupération desmessages d'erreurs qui ne sont donc pas dans le fichier de sortie */
{FILE *fp,*fopen(const char *, const char*);
int fclose(FILE *);
void filecopy(FILE *);
if (argc == 1) filecopy(stdin); /* si pas d'argument, copie sur la sortie standard */
else
while (--argc >0)
if ((fp = fopen(*++argv,"r")) == NULL)
{fprintf(stderr,"cat : can't open %s\n",*argv);
exit(1);}
else {filecopy(fp);fclose(fp);}
exit(0);
}
void filecopy(FILE *fp) /* recopie du fichier fp sur la sortie standard*/
{int c;
while ((c = getc(fp)) != EOF) putc(c,stdout);
}
Exercice 8.2
#include
int main(int argc, char *argv[])
/*
/* */
{FILE *fp1,*fp2;
/* prototypes */
FILE *fopen(char *FILE, char *mode);
void filecopy(FILE *, FILE *);
switch(argc)
{/* si pas d'argument, copie sur la sortie standard */
case 1 : filecopy(stdin,stdout);break; /* recopie sur la sortie standard */
case 2 :
if ((fp1 = fopen(*++argv,"r")) == NULL) /* erreur sur le fichier d'entrée */
{fprintf(stderr,"cat : can't open %s\n",*argv);
exit(2);
}
else {filecopy(fp1,stdout); fclose(fp1); break;}
case 3 : /* cas général */
if ((fp1 = fopen(*++argv,"r")) == NULL) /* erreur sur le fichier d'entrée */
{ fprintf(stderr,"cat : can't open %s\n",*argv); exit(1);}
else
/* erreur sur le fichier de sortie */
if ((fp2 = fopen(*++argv,"w")) == NULL)
{ fprintf(stderr,"cat : can't open %s\n",*argv); exit(2);}
else /* pas d'erreur*/
{filecopy(fp1,fp2);
fclose(fp1);
fclose(fp2);
break;
}
default : fprintf(stderr,"cat : use two files"); exit(3);
}
exit(0);
}
void filecopy(FILE *fp1, FILE *fp2) /* recopie du fichier fp1 dans le fichier fp2 */
{int c;
while ((c = getc(fp1)) != EOF) putc(c,fp2);
}
Exercice 8.3
#include
#include
#define Taille 10
#define Nombre_structure 5
typedef struct chaine {float Tab[Taille]; chaine * pred;} chaine;
void main (void)
{ int i;
chaine * debut, * nouvelle, *ancienne;
chaine * initialiser(void);
void remplir(chaine *, int);
void chainer(chaine *, chaine*);
void parcourt(chaine*);
/* initialisation de la liste */
printf( "debut : Taille chaine : %d\n", sizeof(chaine)) ;
debut=initialiser();
remplir(debut,0);
/* creation de la liste */
for (i=0; i < Nombre_structure;i++)
{nouvelle=initialiser();
remplir(nouvelle,i+1);
chainer(nouvelle,debut);
debut=nouvelle;
}
parcourt(nouvelle);
}
chaine * initialiser(void)
{chaine * nouveau;
nouveau=(chaine*)calloc(1,sizeof(chaine));
printf("nouveau = %x\n",nouveau);
return nouveau;
}
void remplir(chaine * courante,int n)
{int i;
for(i=0; i < Taille; i++) courante->Tab[i]=(float) n*i;
courante->pred=(chaine *) NULL;
printf("fonction remplir\n");
}
void chainer(chaine * nouvelle, chaine * precedente)
{nouvelle->pred=precedente;
printf("fonction chainer\n");
}
void parcourt(chaine * depart)
{int i;
cout Tab[i]);
printf("\n");
printf("%x \n",depart->pred);
depart=depart->pred;
}
}
// résultat
debut : Taille chaine : 44
nouveau = 8049ba0
fonction remplir
nouveau = 8049bd0
fonction remplir
fonction chainer
nouveau = 8049c00
fonction remplir
fonction chainer
nouveau = 8049c30
fonction remplir
fonction chainer
nouveau = 8049c60
fonction remplir
fonction chainer
nouveau = 8049c90
fonction remplir
fonction chainer
parcourt
0 5 10 15 20 25 30 35 40 45
8049c60
0 4 8 12 16 20 24 28 32 36
8049c30
0 3 6 9 12 15 18 21 24 27
8049c00
0 2 4 6 8 10 12 14 16 18
8049bd0
0 1 2 3 4 5 6 7 8 9
8049ba0
INDEX
INDEX-,35
,35
!,35
!=,35
",32
#define,65
#else,71
#endif,71
#if,71
#ifdef,71
#ifndef,71
#include,11, 65
%,35
&,38
&&,35
(),29
() ? :,45
*,35
/,35
/bin/ar,196
/bin/cc,191
/bin/ld,195
/lib,17, 196
/usr/include,11, 17
/usr/lib,17, 196
;,10
^,38
__DATE__,240
__FILE__,240
__LINE__,240
__STDC__,240
__TIME__,240
_exit,170
|,38
||,35
~,39
+,35
++,35
,39
0,33
0x,66
0X,66
a.out,191
abort,232
abs,232
accès aléatoire,155
access,146
acos,229
ADA,220
adb,210
admin,218
adressage,22
adressage symbolique,189
adresse,84
adresse symbolique,27
agrégat,115
ajout de n caractères d'une chaîne dans une autre,235
allocation dynamique de mémoire,233
alphanumérique,189
American Standard Code for Information Interchange,243
analyse syntaxique,64, 191
analyseur syntaxique,76, 190
appel de fonction,15, 77
ar,196
arc cosinus,229
arc sinus,229
arc tangente,229
argc,103
arguments effectifs,77
argv,103
as,200
ASCII,243
asctime,235
asin,229
assembleur,189, 191
atan,229
atan2,229
atexit,232
atof,232, 238
atoi,233, 238
atol,233, 238
attributs des fichiers,147
auto,54
avertissement,74
bibliothèque,157, 194
bibliothèque mathématique,157
bibliothèque standard,157, 194
bibliothèque standard de gestion des entrée/sortie,157
bit de bourrage,126
bit de contrôle,243
bit le plus signicatif,245
bits de poids faible,245
bits de poids fort,245
bloc,19
boucle,12, 45
branchement,137
bsearch,183, 233
buffering,158
C beautifuler,190
calcul de la longueur d'une chaîne de caractères,101
calloc,110, 233
caractère de conversion d'impression,162
caractère d'échappement,32
caractère multioctest,239
case,52
cast,29
cb,190
CC,220
ceil,229
cflow,203
chaîne de caractères,28, 33
champ,115
champ de bits,126
char,25, 27
chemin d'accès,146, 158
chmod,146
chown,149
cible,211
classe d'allocation,54
classe de mémorisation,21
classe par défaut,58
clé,196
clearerr,230
clock,235
close,146, 154, 175, 237
COBOL,220
codage,242
code exécutable,189
code source,189
coercition,29
comparaison de chaînes de caractères,103, 234
comparaison de deux chaînes de caractères,235
comparaison de deux régions mémoire,234
comparaison de pointeurs,96
compilateur,191
compilateur conforme,237
compilation,191
compilation conditionnelle,64
compilation optionnelle,71
compilation séparée,80
compilation simultanée,80
complément à 1,246
complément à 2,246
comportement dépendant de l'implémentation,237
comportement indéfini,237
comportement non spécifié,237
concaténation de chaînes de caractères,234
concaténation de deux chaînes de caractères,83, 273
const,26
constante,66
constante de type caractère,67
constante en virgule flottante,66
constante entière,66
constante manifeste,65
constante symbolique,65
conversion de pointeurs,96
conversion d'une chaîne de caractère en entier long,234
conversion d'une chaîne de caractère en un nombre flottant,234
conversion d'une chaîne de caractère en un nombre long non signé,234
conversion d'une chaîne de caractères en entier,233
conversion d'une chaîne de caractères en entier long,233
conversion d'une chaîne de caractères en flottant,232
conversion explicite,29
conversion implicite,28
conversion temporaire forcée,29
copie de chaîne,272, 273
cos,229
cosh,229
cosinus,229
cosinus hyperbolique,229
cpp,193
CPROLOG,220
creat,146, 149, 151
création,146, 151
cscope,203
C-shell,220
ctime,235
ctrace,200
ctype,238
ctype.h,227
cxref,204
cycle de vie d'un fichier,146
dbx,192
déclaration,21
déclaration du type du résultat d'une fonction,79
déclaration struct,115
default,52
defined,240
définition,21, 115
définition d'une fonction,74
définition littérale,32
délimiteur,19
délimiteur de fin de chaîne,33
delta,219
dépendances,211
dépilement,137
déplacement (overlay possible) d'une région mémoire,234
descripteur de fichier,150
destruction,146
diagramme de la définition d'une fonction,75
difftime,235
directive de compilation,64
directives d'assemblage,189
dis,201
div,233
div,240
div_t,240
division entière,233
do,49
domaine de valeur,74
domaines de définition,74
donnée alphanumérique,243
donnée numérique,243
double,26, 27
double précision,247
droits d'accès,147
dump,201
EBCDIC,243
EBCDIC,243
écriture,146
écriture de données dans un flot,231
écriture d'un caractère dans le flot de la sortie standard,231
écriture d'un caractère dans le flot d'entrée standard,232
écriture d'un caractère dans un flot,231
écriture d'une chaîne de caractères sur la sortie standard,231
ed,190
éditeur de lien,191
éditeurs de textes,190
Edition de lien dynamique,195
Edition de lien statique,195
else...if,46
emacs,190
empilement,137
enjoliveur de programmes,190
enregistrement logique,115
entier indéfini,240
entrées/sorties avec spécification de format,12
entrées/sorties en mode caractère sans spécification de format,11
enum,27, 131
erreur de troncature,246
escape Sequence,32
étiquette,189
exit,233
exit,175
exit,170
exp,229
expression,10, 19
Extended Binary Coded Decimal Interchange Code,243
extern,54
f77,220
fabs,229
fchown,149
fclose,230
fcntl.H,227
fdopen,170
feof,178, 230
fermeture,146, 154
fermeture d'un flot,230
ferror,178, 230
fflush,230
fflush,175
fget,230
fgetc,169, 181
fgetpos,177
fgets,173, 181
fichier,157
fichier banalisé,159
fichier cible,211
fichier delta,218
fichier entête,65
fichier exécutable,192
fichier objet,192
fichier ordinaire,159
fichier standard,158
fichier standard d'affichage des diagnostics d'erreurs,158
fichier standard de sortie,158
fichier standard d'entrée,158
fichiers exécutables,200
fichiers partageables,200
fichiers relogeables,200
FIFO,134
FILE,157, 160
file d'attente,134
fileno,160
first in, first out,134
float,25, 27
floor,229
flot,159
flot de données,159
fmod,230
fonction 91,81, 263
fonction atoi,83
fonction de fonction,76
fonction exponentielle,229
fonction logarithme décimal,229
fonction logarithme Neperien,229
fonction récursive,80
fonction sans argument,78
fonctions hyperboliques,229
fonctions mathématiques,229
fonctions trigonométriques,229
fopen,230
for,47
format ELF,200
forme ANSI,76
forme normalisée,248
forme traditionnelle,76
fpos_t,177, 240
fprintf,172, 181, 230, 238
fputc,169, 171, 181, 230
fputs,173, 181, 230
fread,174, 181, 230
free,110
freopen,170, 230
fscanf,172, 181, 231, 238
fseek,177, 231
fsetpos,177, 178, 231
fsync,175
ftell,177, 178, 231
fwrite,174, 181, 231
gcc,220
gestion des messages d'erreur,234
get,219
getc,171, 181, 231
getchar,11, 161, 181, 231
getenv,233
gets,167, 181, 231
gmtime,235
goto,49, 51
grand caractère,239
graphe des dépendances,210
groupe,147
header,65
help,219
hexadécimal,66
identificateur,21
identificateur de fonction,76
if...else,45
implémentation conforme,237
impression des composantes d'un tableau,45
impression d'un message d'erreur sur le flot standard,231
impression d'un texte formaté dans un flot,232
inclusion de fichiers,64
indicateur de position,177
indirection,84
initialisation,60
initialisation d'un pointeur,96
initialisation d'une structure,116
instruction,19
instruction composée,10
instruction généralisée,19
instruction simple,10
instructions d'assemblage,189
int,25, 27
intégrité,82
interruption,137
invariant de boucle,26
isalnum,227
isalpha,227
isascii,227
iscntrl,227
isdigit,227
isgraph,227
islower,227
isprint,227
ispunct,227
isspace,227
isupper,227
isxdigit,227
item,174
jump_buf,240
Korn-shell,220
label,189
labs,233
last in, first out,134
LC_ALL,239
LC_COLLATE,238
LC_NUMERIC,238
LC_TIME,238
LC_TYPE,238
lconv,240
ldiv,233
lecture,146
lecture et formatage d'un texte d'un flot,231
Lelisp,220
libc.a,157
libc.so,157
librairie,157
librairies,194
LIFO,134
limits.h,228
link,146
lint,190
liste,133
liste chaînée,134
liste d'énumération,131
localeconv,239, 240
localtime,236
log,229
log10,229
long,26, 27
long double,26, 27
long float,240
long int,27
longjump,240
longueur,163
longueur d'une chaîne de caractères,235
lorder,201
lprof,208
lseek,146, 155
L-valeur,121
Lvaleur,23
Lvaleur modifiable,23
Lvalue,23
macrodéfinition,65
macrodéfinition de type fonction,65
macrodéfinition de type objet,65
macrodéfinitions de type fonction,65
macro-substitution du premier ordre,66
magic number,199
main,10
make,210
makefile,211
Makefile,211
malloc,110, 233
math.h,228
mblen,233, 239
mbstowcs,239
mbtowc,239
mcs,201
memchr,234
memcmp,234
memcpy,234
memmove,234
memset,234
message d'erreur,74
mkfifo,149
mknod,149
mode d'ouverture,169
modificateur,164
modification de l'indicateur de position,231
modulo,35
mon.out,205
most significant bit,245
multibytes,239
nm,201
noalias,26
nom externe,146, 158
nom interne,158
nombre de caractères d'une sous-chaîne absent d'une chaîne,235
nombre de caractères d'une sous-chaîne dans une chaîne,235
nombre de chiffres significatifs,164
nombre entier non signé,245
nombre entier signé,245
nombre magique,199
nombre variable d'arguments,237
non signé intégral,40
normes de compilation,193
octal,66
open,146, 149, 237
opendir,150
opérateur .,117
opérateur ->,117
opérateur de coercition,18, 29
opérateur de déréférenciation,84
opérateur de transtypage,29
opérateur ternaire,45
opérateurs binaires,34
opérateurs définis sur les bits,38
opérateurs unaires,34
opérations sur les bits,37
optimiseur de code,191
options de compilation,192
ordre des définitions,76
ordre d'évaluation,43
ouverture,146, 150
ouverture d'un flot,230
passe,189
pc,220
perror,231, 234
picture element,251
pile,137
pile d'exécution,82
pile logicielle,137
pile matérielle,137
pipe,149
pixels,251
pointeur,84, 133
pointeur de fichier,159
pointeur de pile,137
pointeur générique,97
POP,137
POPD,137
portée,54
postdécrémentation,35
postincrémentation,35, 36
pow,230
précision,164
prédécrémentation,35
préincrémentation,35
préprocesseur,64, 191
primitive d'entrées/sorties de bas niveau,147
principe de localisation,46
printf,161, 181, 231, 238
printf,112
procédure,78
prof,193, 205
profileur,205
prog.cnt,208
programme conforme,237
programme strictement conforme,237
propriétaire,147
prototype,15, 76, 79
prototype des fonctions sans argument,78
prototype forme K&R,77
prs,219
ptrdiff_t,240
puissance,230
PUSH,137
putc,171, 181, 231
putchar,11, 161, 181, 231
puts,167, 181, 231
qsort,183, 233
quadruple précision,240
qualificatif,25
racine carrée,230
rand,233
ranlib,197
read,146, 237
realloc,110, 233
réallocation dynamique de mémoire,233
recherche de la marque de fin de fichier,230
recherche d'une chaîne de caractères dans une autre,235
recherche d'une sous-chaîne de caractères dans une autre,234
recopie d'une chaîne dans une autre,235
recopie d'une région de mémoire,234
récupération de la valeur de l'indicateur de position,231
récursivité,80, 116
réels DCB,246
réels décimaux fixes,247
réels fixes,247
réels flottants,247
référence,84, 189
référence en avant,54, 189
référence externe,189
référence interne,189
register,54, 60
règles de précédence,216
règles de production,211
règles d'inférence,216
règles explicites,216
règles implicites,216
remove,231
remplissage d'un tampon avec un caractère,234
rename,231
renommage d'un fichier,231
réouverture d'un flot,230
requête de mémoire pour un caractère,234
reste de la division modulo,230
résultat d'une fonction,79
return,15, 79
rewind,178, 232
Rvaleur,23
R-valeur,121
saisie d'un caractère du flot de l'entrée standard,231
saisie d'un caractère d'un flot,230, 231
saisie d'une chaîne de caractères du flot de l'entrée standard,231
scanf,112, 166, 181, 232, 238
SCCS,218
sdb,210
setbuf,175
setlocale,239
setvbuf,175
shell de Bourne,220
shellscript,220
short,26, 27
short int,27
signed,26, 27
signed int,27
signed long,27
signed long int,27
signed short,27
signed short int,27
simple précision,247
sin,229
sinh,229
sinus,229
sinus hyperbolique,229
size,202
size_t,40, 97, 240
sizeof,40
Source Code Control System,218
soustraction de pointeurs,96
spécification de format,161
spécification d'impression,165
sprintf,180, 181, 232, 238
sqrt,230
sscanf,180, 181, 232
stack pointer,137
stat,146
static,54
stdarg.h.,112
stddef.h,40
stderr,158
stdin,158
stdio.h,71, 230
stdout,158
strcat,83, 234
strchr,234
strcmp,102, 234
strcoll,234, 238
strcpy,235
strcspn,235
stream,159
strerror,235
strftime,238
strictement conforme,237
string.h,234
string-izing,69
strip,201, 202
strlen,235
strncat,235
strncmp,235
strncpy,235
strspn,235
strstr,235
strtod,233, 238
strtok,235
strtol,234, 238
strtoul,234, 238
struct,27, 115
structure,115
structure de bloc,19
structure de contrôle,14
structure de données,115, 137
structure FILE,159
strxfrm,238
substitution symbolique,64, 66
suite de Fibonacci,80
suppression d'un fichier,231
surcharge d'opérateurs,124
switch,52
symbole,189
symbolic debugger,210
symlink,149
system,155, 234
table,134
table des symboles,189
tableau,27
tableau de structures,124
tan,229
tangente,229
tangente hyperbolique,229
tanh,229
terminaison immédiate d'un programme,232
test,45
time,236
tmpfile,232
tmpnam,232
tmpnam,170
token,19, 65
tolower,227
touch,218
toupper,227
transcodage,242
transformation d'une chaîne en token,235
transmission des arguments,81
transmission des arguments par adresse,88
transmission par adresse,81
transmission par référence,82
transmission par valeur,82
transtypage,29
trigraps,239
truss,202
tsort,197
type,21
type agrégat,24
type arithmétique,23
type composite,23
type de base,23
type de structure abstrait,116
type dérivé,25
type du résultat d'une fonction,74
type entier non signé,24
type entier signé,24
type énuméré,24
type flottant,24
type fonction,24
type incomplet,24, 63
type intégral,25
type majeur,25
type non qualifié,25
type objet,25
type pointeur,24
type prédéfini,23
type qualificatif,24
type scalaire,24
type structuré,24
type synonyme,132
type tableau,24
type union,25
typedef,27, 132
undef,67
ungetc,232
union,27, 129
unité syntaxique,65
unlink,146
unsigned,26, 27
unsigned char,27
unsigned int,27
unsigned long,27
unsigned long int,27
unsigned short,27
unsigned short int,27
va_arg,111, 112, 237
va_end,111, 112, 237, 240
va_list,111, 112, 240
va_start,111, 112, 237
valeur absolue,229
valeur absolue d'un entier long,233
valeur absolue d'un nombre entier,232
variable,21
variable automatique,54, 58
variable de localité,238
variable externe,54
variable externe-statique,58
variable globale,54
variable interne-statique,58
variable locale,54, 57
variable pointeur,84
variable qualifiée invariable entière,32
variable qualifiée invariable,32
variable qualifiée invariable,31
variable qualifiée invariable de type caractère,32
variable qualifiée invariable en virgule flottante,32
variable register,54
variable statique,54, 58
vérification de type,15
vfprintf,232, 238
vidage du tampon,230
void,15, 27
void,78
volatile,26
vprintf,232, 238
vsprintf,232, 238
wchar_t,240
wcstombs,239
wctomb,239
what,219
while,47
wide character,239
write,146, 237
xdbx,210
xedit,190
XWindow,190, 221
zone,115
PAGE116
PAGE8
PAGE
TYPES, OPERATEURS ET EXPRESSIONS
II - PAGE2
STRUCTURES DE CONTROLE
III - PAGE14
PAGE1
PAGE
PAGE1
PAGE
PAGE42
PAGE295
PAGE1
PAGE1
PAGE1