Un flux (stream) est une suite, potentiellement infinie, de données, qui évoluent dans le temps.
Un programme échange des données avec l'extérieur via des flux. On distingue les flux d'entrée et de sortie.
En C, comme dans d’autres langages, il existe des flux prédéfinis : - stdin : flux d’entrée standard (par défaut, le clavier)
- stdout : flux de sortie standard (par défaut l’écran) - stderr : flux standard d’erreur (par défaut l’écran)
On peut créer des flux pour lire les valeurs d'un capteur, écrire sur une imprimante, lire ou écrire dans des fichiers, lire les touches tapées au clavier, afficher sur l'écran, ...
Notion de flux
La création d’un flux sur un fichier est réalisé par l’appel à la fonction fopen (qui renvoie NULL si le fichier est inexistant ou inaccessible).
FILE est un synonyme (donc en majuscule) de type prédéfini.
Il faut toujours fermer le flux quand on ne s’en sert plus.
Flux sur un fichier
#include <stdio.h>
FILE* monflux = fopen("./toto.txt","w");
...
fclose(monflux);
Modes d’ouverture d’un flux :
r : lecture à partir du début de fichier w : écriture à partir du début de fichier a : écriture seule à la fin du fichier
rb, wb, ab : lecture ou écriture en mode binaire (plus léger mais non portable) r+, w+, a+ : ouverture en lecture et écriture
Lecture/écriture dans un fichier
FILE* f = fopen("./truc.txt","w");
fputs("au revoir 2019 \n",f);
fputc('\n',f);
fprintf(f,"%s - ","bonjour 2020");
fclose(f);
char* message; // on peut utiliser le type string int annee;
f = fopen("./truc.txt","r");
fgets(message,80,f);
fscanf(f,"%s %d",message,annee);
fclose(f) ;
int t[] = {2,3,4,5};
FILE* f = fopen("./truc.txt","wb");
fwrite(&t,sizeof(int),sizeof(t)/sizeof(int),f);
fclose(f);
f = fopen("./truc.txt","rb");
int tb[4];
fread(&tb,sizeof(int),4,f);
Sauvegarde/chargement d’un tableau dans un fichier binaire :
lit jusqu’à 80 caractères ou jusqu’à la fin de la ligne ou jusqu’à la fin du fichier
écrit « au revoir 2019 » dans le fichier
Sauvegarde/chargement d’un enregistrement dans un fichier binaire :
Lecture/écriture dans un fichier
struct Personne{
int age;
char *nom;
};
struct Personne toto = {27,"Toto"};
FILE* f = fopen("./truc.save","wb");
fwrite(&toto, sizeof toto,1,f);
fclose(f);
f = fopen("./truc.save","rb");
struct Personne titi;
fread(&titi,sizeof titi,1,f);
fclose(f);
Quand on lit un flux, il peut être utile de savoir si on est au bout.
fgets renvoie NULL si aucune valeur n’est lue.
Fin de fichier
FILE* f = fopen("./truc.txt","r");
while(fgets(message,20,f)!=NULL){
printf("%s\n",message);
}
fclose(f);
En cas d'erreur à l'exécution, il est souvent difficile de repérer la ou les instructions qui ont causé l'erreur.
Il est possible de placer des affichages à des endroits stratégiques pour contrôler ce qui se passe :
Erreurs et tests
...
printf("c'est parti\n");
...
printf("chargement du fichier %s ...",nomFichier);
...
printf("ok\n");
...
printf("debut des calculs\n");
while(...){
printf("tour de boucle numero : %d\n",i);
...
} ...
Erreurs et tests
Mais une fois le programme au point, il faut enlever les affichages.
On peut aussi utiliser une constante symbolique qui permet de basculer du mode débogage au mode normal.
#define MODE 1 // 1:mode debugage ...
if(MODE==1) printf("c'est parti\n");
...
if(MODE==1) printf("chargement du fichier %s ...",nomFichier);
...
if(MODE==1) printf("ok\n");
...
if(MODE==1) printf("debut des calculs\n");
while(...){
if(MODE==1) printf("tour de boucle numero : %d\n",i);
...
}
Il existe des outils pour déboguer les programmes à l'exécution, par exemple GDB Pour l'utiliser, il faut compiler le programme avec l'option -g
GDB
> gcc -g -o toto monprogramme.c
> gdb toto
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
...
(gdb) run
Starting program: /bidule/test c'est parti
Program received signal SIGSEGV, Segmentation fault.
0x804865a in affiche (M=0x8049af8, nb_lignes=1073928121, nb_col=134513804) at monprogramme.c:38
(gdb) quit
>
Il existe de nombreuses commandes dans GDB pour analyser les erreurs, comme backtrace qui liste les appels de fonction, print pour afficher les valeurs des variables au moment de l'erreur, ...
Voir la documentation sur www.gnu.org/software/gdb/
En C, et dans certains autres langages, il est possible de poser des assertions, qui entraînent l'arrêt du programme si elles sont violées.
Assertions
Les assertions peuvent être :
- des préconditions, qui doivent être vérifiées à l'entrée d'un bloc de code - des post-conditions, qui doivent être vérifiées à la sortie d'un bloc de code - des invariants qui doivent être vérifiés tout au long d'une boucle ou d'un bloc Les assertions peuvent être désactivées en redéfinissant la constante NDEBUG avant l'inclusion de assert.h
#include <assert.h>
...
assert(i==1);
...
> ./test
test: test.c:15: main: Assertion `i==1' failed.
Aborted (core dumped)
>
...
D'autres mécanismes de contrôle des erreurs :
- les exceptions permettent de remonter des erreurs d'un contexte d'exécution vers le contexte supérieur. Ce mécanisme n'existe pas en C.
- de nombreuses techniques et modèles de vérification formelle de programme voire de preuve de programme existent pour garantir leur bon fonctionnement
Le meilleur mécanisme permettant d'éviter les bugs reste le cerveau du programmeur !
Contrôle des erreurs d'exécution
We know that numerical calculations are generally the stumbling-block to the solution of problems, since errors easily creep into them, and it is by no means always easy to detect these errors.
Ada Lovelace (1815-1852) - Notes on the Sketch of The Analytical Engine.