Les entrĂ©es/sorties (E/S ou I/O) s’appliquent sur:

  • Les flux standards:
    • Entree standard (stdin, n°0)
    • Sortie standard (stdout, n°1)
    • Sortie d’erreur (stderr, n°2)
  • Des fichiers en utilisant des objets appelĂ©s descripteurs de fichiers

Exemple de redirection: ls 2> /dev/null: permet de rediriger le flux stdout vers /dev/null.

Les entrées sorties peuvent se faire en utilisant soit les fonctions de haut-niveau ou bas-niveau.

Principe général

Pour faire des entrées/sorties, on va:

  1. Ouvrir le fichier (en lecture ou en écriture). On obtient un descripteur
  2. OpĂ©rations de lectures et/ou d’écritures en utilisant le descripteur de fichier.
  3. Fermer le descripteur de fichier

Les lectures/écritures peuvent se faire de maniÚre formattées ou non.

Indispensable

Il est NECESSAIRE de s’assurer que le fichier est bien Ă©crit sur son support. Sinon des donnĂ©es vont ĂȘtre perdues/corrompues.

APIs

Fonctions haut-niveau

Ces fonctions sont fournies par la bibliothĂšque standard libc. Elles sont indĂ©pendantes du systĂšme d’exploitation, c’est une interface accessible sur tout les systĂšmes courants.

Libc fourni également les descripteurs de fichiers, qui sont de type File*

Bas niveau

A ce niveau, les fonctions sont fournies par le systÚme et sont dépendantes de chaque systÚme mais permettent des opérations plus spécifiques. (ex. permissions, pipelines
)

Formattées vs non-formattées

Par formattĂ©, on entend par lĂ  que les donnĂ©es ont Ă©tĂ© converties d’une reprĂ©sentation binaire en mĂ©moire Ă  une reprĂ©sentation texte dans le fichier.

Au contraire, non-formatté signifie que les données sont au format binaire.

Exemple: La valeur 12 reprĂ©sentĂ©e en mĂ©moire sur un octet sera prĂ©sentĂ©e au format texte avec 2 caractĂšres: ‘1’ et ‘2’. On aura donc 00001100 en binaire, et 00110001 00110010 au format texte

Résumé

Formatté = texte

Non-formatté = binaire

Descripteurs de fichiers

Work in progress

En bas niveau, descripteurs de fichiers = type int

API Haut-niveau

Documentation: page de man

Au haut-niveau, on doit importer la librairie stdio de cette façon:

#include <stdio.h>

stdio = standard i/o

Pour ouvrir un fichier:

File* fopen (const char* name, const char* mode)

Avec les arguments suivants:

  • name: nom du fichier Ă  ouvrir
  • mode: Le mode de lecture
    • r: lecture
    • w: Ă©criture
    • r+: Lecture/Ă©criture
    • w+: Lecture/Ă©criture, Le fichier est tronquĂ©. (Le fichier sera vidĂ©)
    • a: Ă©criture en ajout Ă  la fin
    • a+: lecture/Ă©criture en ajout

La fonction fopen renverra un descripteur de fichier ou la valeur NULL si il y a une erreur.

Variantes:

  • fdopen
  • freopen

Fermer un fichier

int fclose(File* f)

Permet de fermer le descripteur donnĂ©. Retournera 0 en cas de succĂšs et EOF en cas d’échecs

Documentation sur EOF: wikipedia.org

Exemple: Ouvrir un fichier ‘toto.txt’

En lecture:

File *f;
f = fopen("toto.txt", "r");
 
if (f == NULL) {
	perror("fopen");
	exit(-1);
}
 
// opérations de lectures...
 
if (fclose(f)) { // car si succĂšs, alors = 0 donc false
	perror("fclose");
}

En lecture/écriture:

Par octet:

// lecture
int getc(File* stream)
int fgetc(File* stream)
int getchar(void)
 
// écriture
int putc(int c, File* stream)
int fputc(int c, File* stream)
int putchar(int c);

Par bloc:

size_t freed(void *ptr, site_t size, size_t nmemb, File* stream)
size_t fwrite(const void *ptr, size_t size, size_t nmemb,File* stream)

Lecture (écriture) de nmemb objets de taille size vers mémoire pointée par ptr depuis (vers) le descripteur Stream

Exemple

// lecture
int bob[50];
size_t n;
 
n = fread(bob, sizeof(int), 50, f)
if (n != 50) { /* message */ }
 
// avec des char
char ligne[50]
size_t nl;
 
nl = fread(ligne, 1, 50, f) // char de taille '1'
if (n != 50) { /* message */ }

Formatées

// sur stdout
int printf(cpnst char* format, ...)
// print sur le fichier de descripteur F
int fprintf(File* f, const char* format, ...)
// En mémoire à l'adresse pointée par S.
int sprintf(char* s, const char* format, ...)
// comme sprintf mais écrit au plus 'n' caractÚres
int snprintf(char* s, size_t n, const char* format, ...)

Méthode sprintf à éviter

// Depuis stdin
int scanf(const char* format, ...)
// Depuis le fichier 'f'
int fscanf(File* f, const char* format, ...)
// Depuis la chaßne à l'adresse primaire pointée par 's'
int sscanf(const char* s, const char* format, ...)

Remarques TP

Exercice 1

Consignes:

Écrire une fonction :

int copie(FILE *entree, FILE *sortie)

qui copie le contenu du fichier de descripteur entree dans le fichier de descripteur sortie. La fonction doit retourner le nombre d’octets copiĂ©s, ou -1 en cas d’erreur. La copie peut se faire par caractĂšre avec getc() / putc() ou bien par bloc avec fread() / fwrite().

Tester cette fonction en Ă©crivant un programme principal l’utilisant avec la copie de deux fichiers : un fichier texte (par exemple le code source du programme), et un fichier binaire (par exemple un fichier exĂ©cutable). Vous pourrez utiliser la commande cmp(1) pour vĂ©rifier que la copie est conforme Ă  l’original.

Code:

#include <stdio.h>
#include <stdlib.h>
 
// Retourne le nombre de caractÚres copiés ou -1 si erreur
int copie(FILE *entree, FILE *sortie) {
	int nb_copies = 0;
	int c = getc(entree);
	while (c != EOF){
		if (putc(c, sortie) == EOF)
			break; // erreur, putc a échoué
		c = getc(entree);
		nb_copies++;
	}
 
	if (ferror(entree) || ferror(sortie)) {
		return -1;
	} else return nb_copies;
}
 
int main(int argc, char *argv[]) {
	if (argc != 3) {
		fprint(stderr, "Usage: %s entree sortie\n", argv[0]);
		exit(1);
	}
 
	// arguments de la ligne de commande
	const char *fichier_entree = argv[1];
	const char *fichier_sortie = argv[2];
 
	FILE *entree = fopen(fichier_entree, "r");
	if (entree == NULL) {
		perror("fopen(entree)");
		exit(2);
	}
	FILE *sortie = fopen(fichier_sortie, "w");
	if (sortie == NULL) {
		perror("fopen(sortie)");
		exit(2);
	}
 
	int n = copie(entree, sortie);
	if (n >= 0){
		printf("Copie de %d caractĂšre(s)\n", n);
	} else {
		printf("Erreur de copie\n");
	}
 
	// fermeture des fichiers
	if (fclose(sortie) != 0) {
		perror("fclose(sortie)")
	}
	if (fclose(entree) != 0) {
		perror("fclose(entree)");
	}
	return 0;
}

Bas-niveau

L’interface est fournie par le systùme d’exploitation. Les descripteurs sont de type int.

En particulier:

  • 0 pour l’entrĂ©e standard (STDIN_FILE)
  • 1 pour la sorte (STDOUT_FILE)
  • 2 pour la sortie d’erreur (STDERR_FILE)

Pipelines

TODO