« Fonctions de base en langage C » : différence entre les versions

Une page de Wikiversité, la communauté pédagogique libre.
Contenu supprimé Contenu ajouté
Thierry46 (discussion | contributions)
m Correction lien entre leçons
Thierry46 (discussion | contributions)
m →‎Gestion de mémoire : complément titre
Ligne 713 : Ligne 713 :
</source>
</source>


=== Gestion de mémoire ===
=== Gestion de la mémoire ===


{{loupe|amorce=Pour la syntaxe des fonctions d'allocation mémoire, voir le wikilivre |b:Programmation C/Gestion de la mémoire{{!}}Programmation C, chapitre sur la gestion de la mémoire}}
{{loupe|amorce=Pour la syntaxe des fonctions d'allocation mémoire, voir le wikilivre |b:Programmation C/Gestion de la mémoire{{!}}Programmation C, chapitre sur la gestion de la mémoire}}

Version du 1 juin 2008 à 13:01

Début de la boite de navigation du chapitre
Fonctions de base
Icône de la faculté
Chapitre no {{{numéro}}}
Leçon : Langage C
Chap. préc. :Pointeurs
Chap. suiv. :Le préprocesseur
fin de la boite de navigation du chapitre
En raison de limitations techniques, la typographie souhaitable du titre, « Fonctions de base en langage C : Fonctions de base
Fonctions de base en langage C
 », n'a pu être restituée correctement ci-dessus.

Présentation des fichiers d'entête de base fournis avec la plupart des compilateurs C et des fonctions qu'ils contiennent [1].

assert.h

Description

Ce fichier d'include ne définit qu'une seule macro : assert(expression). Si expression est fausse (ou égale à 0), alors un message d'erreur est affiché et le programme est arrêté par abort :

Type de message d'erreur :

"assertion \"%s\" failed: file \"%s\", line %d\n"
"expression", __FILE__, __LINE__);

Si vous définissez la macro NDEBUG, la macro assert n'est pas prise en compte.

Exemple

Le programme assert.c s'arrête par assert si on lui passe des paramètres.

// Compilation : gcc -Wall -pedantic -o assert.exe assert.c
// Compilation en ignorant assert : gcc -DNDEBUG -Wall -pedantic -o assert.exe assert.c
#include <stdio.h>
#include <assert.h>
int main(int argc, char *argv[])
{
   assert(argc == 1);
   (void)puts("Appel sans parametre.");
   return 0;
}

Résultats d'exécution :

MacMini-TM:~/Documents/exemples_c thierry$ ./assert.exe Appel sans parametre.
MacMini-TM:~/Documents/exemples_c thierry$ ./assert.exe  param
assert.c:5: failed assertion `argc == 1'
Abort trap

Remarques

  • Un message d'erreur tel que celui généré par assert n'est pas acceptable dans un code de production : il n'est pas compréhensible par un utilisateur et ne lui permet pas de savoir d'où vient le problème.
  • assert peut être cependant utile pendant les phases de mises au point. Les erreurs détectées doivent être prises en compte d'une autre façon et exprimées par des messages plus compréhensibles. Les autres assertions qui n'ont pas été rencontrées pourront être laissées dans le code et rendues inactives à l'aide de NDEBUG.
  • Il peut être désorientant qu'un programme se comporte différemment selon la façon de le compiler.

ctype.h

Description générale

L'ordre des caractères peut dépendre de la norme de codage utilisée sur les plate-formes. La bibliothèque standard du langage C offre des fonctions de classification et de conversion (majuscules, minuscules) qui permettent de s'en affranchir.

Les fonctions

classification de caractères

Les fonctions suivantes ont des arguments du type int, dont la valeur est EOF ou peut être représentée comme unsigned char.

  • int isupper(int C)  : retourne une valeur différente de zéro, si C est une majuscule
  • int islower(int C)  : retourne une valeur différente de zéro, si C est une minuscule
  • int isdigit(int C)  : retourne une valeur différente de zéro, si C est un chiffre décimal
  • int isalpha(int C)  : retourne une valeur différente de zéro, si islower(C) ou isupper(C)
  • int isalnum(int C)  : retourne une valeur différente de zéro, si isalpha(C) ou isdigit(C)
  • int isxdigit(int C)  : retourne une valeur différente de zéro, si C est un chiffre hexadécimal
  • int isspace(int C)  : retourne une valeur différente de zéro, si C est un signe d'espacement

Conversion de caractères

Elles fournissent une valeur du type int qui peut être représentée comme caractère; la valeur originale de C reste inchangée:

  • int tolower(int C)  : retourne C converti en minuscule si C est une majuscule, sinon C
  • int toupper(int C)  : retourne C converti en majuscule si C est une minuscule, sinon C

errno.h

Des informations sur la gestion des erreurs dans le wikilivre Programmation C, chapitre sur les erreurs.

Le fichier d'entête errno.h définit le mécanisme qui permet de savoir si une fonction système ou une fonction de la bibliothèque standard a rencontré une erreur. Elle écrit alors une valeur entière dans errno.

Remarques sur l'utilisation de errno

  • une fonction qui s'exécute avec succès ne remet pas à 0 errno, vous devez vous en charger avant de l'appeler.
  • errno peut ne pas être une variable : ne pas utiliser son adresse.
  • Vous devez stocker ou exploiter au plus tôt errno juste après l'appel de la fonction. Tout appel à une fonction autre que perror risque d'écraser errno.
  • Ne pas utiliser le mécanisme errno dans vos propres fonctions.

Constantes symboliques

errno.h contient la définition de constantes symboliques qui représentent les codes d'erreur de errno.

Ces constantes peuvent être spécifiques à un système. Il faut étudier errno .h sur la machine cible pour assurer la portabilité des programmes.

Fonctions utiles

  • perror() : Affiche le message d'erreur correspondant à errno sur stderr.
  • strerror() : Renvoie le message d'erreur correspondant à errno.

Un extrait de code qui analyse le résultat de la fonction système stat en cas de problème :

cr = stat(nomFichier, &buf);
if (cr != 0)
{
   perror("stat");
   exit(EXIT_FAILURE);
}

math.h

Description

Le fichier d'en-tête <math.h> déclare des fonctions mathématiques. Ces fonctions respectent la norme IEEE 754.

Tous leurs paramètres et résultats sont du type double. Les angles sont indiqués en radians.

Les fonctions

Pour une description détaillée des fonctions mathématiques, voir le WikiLivre Programmation C, chapitre Mathématiques.

Limitations et problèmes

Les nombres flottants sont représentés en machine par une mantisse et un exposant signés codés sur un nombre d'octets limités, par exemple sur ma machine : 4 octets pour un float et 8 pour un double (voir IEEE 754). Les nombre utilisables sont donc un sous-ensemble de l'ensemble mathématique des décimaux .

  • limitations dues au codage de l'exposant (valeurs données à titre d'exemple sur un Mac Mini) :
    • impossible de représenter des grands nombres négatifs et positifs, exemple DBL_MAX = 1.797693e+308.
    • limitations pour les valeurs trop proches de 0 en négatif et positif, exemple DBL_MIN = 2.225074e-308.
  • limitations dues au codage de la mantisse : l'écart entre deux doubles successifs ne peut pas être plus petit que DBL_EPSILON = 2.220446e-16.
  • Les résultats d'opérations ou fonctions peuvent provoquer des dépassements de capacité.

Les erreurs signalées par les fonctions

En cas de problème, les fonctions rangent dans errno (voir errno.h et man de la fonction) :

  • EDOM : Erreur de domaine pour les paramètres : sqrt(-1.0).
  • ERANGE : Erreur si le résultat ne peut pas être représenté sur un double.

stdio.h

Description

Le fichier en-tête stdio.h contient les déclarations d'un ensemble de fonctions qui gèrent les entrées/sorties des programmes écrits en C. On y trouve aussi des constantes utiles pour rendre les programmes portables : FILENAME_MAX, BUFSIZ, EOF..., des fonctions pour renommer, détruire des fichiers et créer des fichiers temporaires.

Les fonctions d'entrée/sortie sont basées sur des mécanismes complexes.

Les fonctions d'entrées sorties

Les fonctions d'entrées sorties reposent sur une structure FILE remplie par la fonction fopen. Dans la suite, FP désignera un pointeur sur une structure FILE (file handle en anglais) associée à une source ou une destination de données liée à un fichier sur disque dur ou sur un autre périphérique. On parle aussi de flux (stream en anglais) de données associé à un fichier externe.

Pour la syntaxe des fonctions d'entrée sortie, voir le wikilivre Programmation C, chapitre sur les entrées/sorties.

Bufferisation

Les fonctions d'entrée/sortie contrairement aux fonctions systèmes comme open, read et write, sont dîtes bufferisée : Les données peuvent être transmises par blocs.

Elles apportent aux programmeurs une gestion automatique de mémoires tampons qui évitent de faire à chaque demande d'entrées/sorties des accès aux périphériques très coûteux en temps.

Les fonctions de la famille de setvbuf permettent de contrôler précisément ce fonctionnement pour optimiser un programme. les fonctions fflush et fpurge permettent de vider ou d'effacer les mémoires tampons à tout moment.

Flux associés par défaut à un programme

Des constantes de type pointeur sur FILE sont ouverts automatiquement :

  • stdin (entrée standard) pointe sur le buffer du clavier
  • stdout (sortie standard) pointe sur le buffer de l'écran
  • stderr (sortie des erreurs) pointe sur la sortie des erreurs associée elle aussi à l'écran. Ce flux provoque une écriture immédiate.

Des fonctions spécifiques sont fournies pour les manipuler :

  • on obtient leur nom en supprimant le f de départ des noms des fonctions d'entrée sortie sur fichier.
  • on ne leur passe pas de paramètre FP (pointeur sur structure FILE *).
  • exemple : printf(...) pour fprintf(stdout, ...), scanf(...) pour fscanf(stdin,...), ...
  • Attention : certaines fonctions spécifiques ne se comportent pas comme leurs homologues :
    • putchar(c) pour fputc(c, stdout);
    • puts(str) ajoute un saut de ligne, contrairement à fputs(str, stdout);
    • gets(str) beaucoup moins sûre que fgets(str, size, stdin) (risque de débordement de str).

Exemple simple d'écriture de texte dans un fichier

Le programme suivant ecritFichier.c écrit une chaine de caractères dans un fichier. Voir ses commentaires pour plus d'explications.

/*
Nom : ecritFichier.c
Role : Écrit Bonjour tout le monde ! dans le fichier bonjour.txt.
Paramètres : non pris en compte.
Code retour : 0 (EXIT_SUCCESS) ou EXIT_FAILURE en cas de problème
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -std=c99 -o ecritFichier.exe ecritFichier.c
Pour exécuter, tapez : ./ecritFichier.exe puis afficher le contenu de bonjour.txt
*/
#include <stdio.h>
#include <stdlib.h>
#define NOM_FIC "bonjour.txt"

int main(void)
{
	// Declaration du descripteur de fichier
	FILE *hFile = NULL;
	int codeRetour = 0;
 
	// Ouvre le fichier bonjour.txt et teste
	hFile = fopen(NOM_FIC, "w");
	if (hFile == NULL)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible d'ouvrir %s en ecriture\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	// Écrire Bonjour tout le monde, dans le fichier
	codeRetour = fputs("Bonjour tout le monde !\n", hFile);
	if (codeRetour == EOF)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible d'ecrire dans %s\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	// Ferme le fichier
	codeRetour = fclose(hFile);
	if (codeRetour == EOF)
	{
		perror("Erreur");
		(void)fprintf(stderr,
			"Impossible de fermer correctement %s\n",
			NOM_FIC);
		exit(EXIT_FAILURE);
	}
 
	(void)printf("texte ecrit dans %s, sortie normale\n",
		NOM_FIC);
 
	return EXIT_SUCCESS;
} // int main(...

Codes Retours

Pour obtenir un programme robuste, il faut impérativement tester les codes retours des fonctions d'entrée/sortie et agir en conséquence : informer l'utilisateur, entreprendre un autre traitement, arrêter le programme.

  • Les tests if (hFile == NULL) et if (codeRetour == EOF) sont nécessaires, mais pas suffisants : l'appel à la fonction perror() permet d'afficher la cause de l'erreur codée dans la variable globale errno sur stderr. Il faut appeler au plus tôt perror après l'erreur, car tout appel système ultérieur modifiera errno en cas de problème.
  • Par exemple, si le fichier bonjour.txt n'est pas accessible en écriture, on obtient :
MacMini-TM:~/Documents/developpement/c thierry$ ./ecritFichier.exe 
Erreur: Permission denied
Impossible d'ouvrir bonjour.txt en ecriture
  • Les causes d'erreur peuvent être nombreuses : fichier non accessible, espace disque insuffisant... :Pour les connaître, consultez le chapitre ERRORS des pages de man des fonctions de niveau 3, exemple : man fputs.
ERRORS
     [EBADF]            The stream argument is not a writable stream.

     The functions fputs() and puts() may also fail and set errno for any of
     the errors specified for the routines write(2).
On voit ici, qu'il faut aussi regarder la page de man de la fonction de niveau 2 (système) write : man -s2 write.
  • Les constantes symboliques correspondant aux codes d'erreur sont dans errno.h
  • Utilisation de la fonction c99 strerror :
// Pour strerror
#include <string.h>
// Pour variable errno
#include <errno.h>
// ...
	if (hFile == NULL)
	{
		char * chErreur = strerror(errno);
		(void)fprintf(stderr,
			"Impossible d'ouvrir %s en ecriture\n"
			"Erreur systeme : %s\n",
			NOM_FIC, chErreur);
		exit(EXIT_FAILURE);
	}
// ...
Il faut sauver ou exploiter errno au plus vite après l'erreur.

Exemple simple de lecture de texte dans un fichier

/*
Nom : litFichier.c
Role : Lit et affiche la chaine de caractère lue dans le fichier bonjour.txt.
Paramètres : non pris en compte.
Code retour : 0 (EXIT_SUCCESS) ou EXIT_FAILURE en cas de problème
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -std=c99 -o litFichier.exe litFichier.c
Pour exécuter, tapez : ./litFichier.exe puis afficher le contenu de bonjour.txt
*/
#include <stdio.h>
#include <stdlib.h>
// Pour constante _POSIX_MAX_INPUT
#include <limits.h>
#define NOM_FIC "bonjour.txt"
 
int main(void)
{
        // Declaration du descripteur de fichier
        FILE *hFile = NULL;
        int codeRetour = 0;
		char chaineLue[_POSIX_MAX_INPUT];
 
        // Ouvre le fichier bonjour.txt en lecture et teste
        hFile = fopen(NOM_FIC, "r");
        if (hFile == NULL)
        {
                perror("Erreur");
                (void)fprintf(stderr,
                        "Impossible d'ouvrir %s en lecture\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        // Tentative de lecture dans le fichier
	if (fgets(chaineLue, _POSIX_MAX_INPUT, hFile) != NULL)
	{
		(void)printf("Chaine lue : %s", chaineLue);
	}
	else if (feof(hFile) != 0)
        {
                (void)fprintf(stderr,
                        "Rien à lire dans %s : fichier vide.\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
	else if (ferror(hFile) != 0)
        {
                perror("Erreur"); // ferror et feof ne modifient pas errno
                (void)fprintf(stderr,
                        "Problème de lecture dans %s.\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        // Ferme le fichier
        codeRetour = fclose(hFile);
        if (codeRetour == EOF)
        {
                perror("Erreur");
                (void)fprintf(stderr,
                        "Impossible de fermer correctement %s\n",
                        NOM_FIC);
                exit(EXIT_FAILURE);
        }
 
        return EXIT_SUCCESS;
} // int main(...

Erreurs fréquentes et recommandations

  • Lors de l'utilisation de printf, une erreur d'exécution fréquente est due à la non concordance en nombre ou type entre les variables à traiter et les spécificateurs de format.
  • Une erreur grave consiste à passer aux fonctions de la famille scanf une valeur au lieu d'une adresse.
  • Il est préférable d'utiliser les fonctions de lecture comme fgets, snprintf qui permettent de spécifier la taille maximale de de la chaine recevant la lecture pour éviter les débordements.
  • Les fonctions fwrite et fread permettent respectivement d'écrire et de lire des données binaires dans un flux. Ce flux doit être ouvert à l'aide de la fonction fopen en spécifiant le caractère b (rb, rb+, wb) pour l'attribut mode : sa présence indique (pour certains systèmes comme MS-DOS) qu'il s'agit d'un flux binaire et non d'un flux caractère. Les données lues ne devront pas être modifiées (caractère ASCII 0xA (\n) transformé en 0xAD (\n\r)). Pour la portabilité de vos programmes qui écrivent ou lisent des données binaires, ne pas l'oublier!
    • La relecture de données binaire sur un même machine ne pose pas de problèmes. Sur des machines différentes, l'exercice pourra être plus difficile : problème d'ordre des octets : little/big endian, taille des types différents.
    • La solution pourrait être :
      • d'utiliser les fonctions ASCII du chapitre précédent si les volumes sont faibles.
      • d'appeler les fonction d'une bibliothèque spécialisée
      • de greffer à votre code un compresseur de données genre gzip si les données sont volumineuses.
  • Les fonctions de positionnement dans un fichier permettent de travailler sur disque avec un volume de données en mémoire minimum, mais les temps d'exécution peuvent augmenter.

Création de fichiers temporaires

Rôle

Les fichiers temporaires servent aux programmes à stocker sur disque des informations qu'il pourra réutiliser plus tard lors de son exécution. Cette manœuvre peut permettre :

  • de traiter les informations un peu plus tard par une fonction séparée.
  • d'économiser de la place en mémoire vive en stockant sur disque, par exemple, des informations relatives à une tâche qui devient moins prioritaire.

tmpfile()

La fonction tmpfile() de stdio.h permet de créer en lecture écriture un fichier qui sera automatiquement effacé lorsqu'il sera fermé. Il sera créé dans un répertoire spécial dont le nom se trouve dans P_tmpdir ou par exemple dans /tmp ou \temp.

Le programme suivant écrit puis relit dix entiers dans un fichier temporaire :

/***************************************************************************
Nom ......... : tmpfile.c
Role ........ : Exemple d'utilisation de la fonction tmpfile
Auteur ...... : Thierry46, licence GNU GPL
Date creation : 7/4/2008

- Compilation et edition de liens :
gcc -Wall -pedantic -o tmpfile.exe tmpfile.c
- Exécution : ./tmpfile.exe
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define TAILLE_BUF 255

int main (void)
   {
   FILE *h_ftemp;
   unsigned int i;
   int nombre;
   char buffer[TAILLE_BUF];
   
   (void)printf("P_tmpdir = %s\n", P_tmpdir);
   /* Ouverture du fichier temporaire */
   (void)puts("Ouverture du fichier temporaire par tmpfile().");
   if ( (h_ftemp = tmpfile()) == NULL )
   {
      perror("Erreur sur tmpfile()");
      exit(EXIT_FAILURE);
   }
   
   /* Rangement dans ce fichier de 10 nombres aleatoires a 1 chiffre */
   (void)puts("Rangement dans ce fichier de 10 nombres aleatoires :");
   for ( i = 0; i < 10; i++)
   {
      nombre = rand() % 10;
      (void)printf("%d\n", nombre);
      (void)fprintf(h_ftemp, "%d\n", nombre);
   }
   
   /* Relecture des nombres aleatoires ranges dans le fichier */
   (void)puts("Relecture des nombres aleatoires ranges dans le fichier :");
   rewind(h_ftemp);
   while ( fgets(buffer, TAILLE_BUF, h_ftemp) != NULL )
      (void)fputs(buffer, stdout); /* Evite l'ajout d'un \n supplementaire */
   
   (void)puts("Fin du programme tmpfile,\n"
      "le fichier temporaire est detruit automatiquement.");
   return EXIT_SUCCESS;
}

tmpnam()

Cette fonction de prototype char *tmpnam(char *str) retourne un nom de fichier temporaire mais ne l'ouvre pas comme tmpfile(). C'est à vous d'ouvrir le ficher et de le détruire. Le nom créé débute par le contenu de la variable P_tmpdir de stdio.h. Vous lui passez en paramètre :

  • NULL : alors le nom sera généré dans une zone statique de tmpnam et pourrait être écraser lors d'un prochain appel.
  • Soit une chaîne de caractère que vous aurez définie de longueur au moins L_tmpnam.

Limite : tmpnam retourne une chaîne de caractères différente pour TMP_MAX appels successifs. TMP_MAX, défini dans stdio.h vaut au minimum 25, pour un système Mac OS X.4 : 308915776.

Exemple d'utilisation :

/***************************************************************************
Nom ......... : tmpnam.c
Role ........ : Exemple d'utilisation de la fonction tmpnam
Auteur ...... : Thierry46, licence GNU GPL
Date creation : 7/4/2007

Pour compilation et edition de liens :
gcc -Wall -pedantic -o test_tmpnam.exe tmpnam.c
Pour exétuter : ./test_tmpnam.exe
***************************************************************************/
#include <stdio.h>
#include <stdlib.h> /* Pour constantes EXIT_ */
#define TAILLE_BUF 255

int main (void)
{
   FILE *h_ftemp;
   char nomfic[L_tmpnam];
   unsigned int i;
   int nombre;
   char buffer[TAILLE_BUF];
   
   (void)printf("TMP_MAX = %d\n", TMP_MAX);

   /* Creation du nom du fichier temporaire */
   (void)puts("Creation du nom du fichier temporaire par tmpnam() :");
   if ( tmpnam(nomfic) == NULL )
   {
      perror("Erreur sur tmpnam()");
      exit(EXIT_FAILURE);
   }
   (void)puts(nomfic);

   /*
   Ouverture du fichier temporaire :
   Le fichier cree sera sous notre responsabilite :
   fermeture, destruction...
   */
   (void)puts("Ouverture du fichier temporaire.");
   if ( (h_ftemp = fopen(nomfic, "w")) == NULL )
   {
      perror("Erreur sur fopen()");
      exit(EXIT_FAILURE);
   }
   
   /* Rangement dans ce fichier de 10 nombres aleatoires a 1 chiffre */
   (void)puts("Rangement dans ce fichier de 10 nombres aleatoires :");
   for ( i = 0; i < 10; i++)
   {
      nombre = rand() % 10;
      (void)printf("%d\n", nombre);
      (void)fprintf(h_ftemp, "%d\n", nombre);
   }
   
   /* On peut fermer le fichier et l'exploiter ulterieurement */
   (void)puts("Fermeture du fichier temporaire.");
   if ( fclose(h_ftemp) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la fermeture du fichier par remove()");
      exit(EXIT_FAILURE);
   }
   
   /* Reouverture du fichier temporaire */
   (void)puts("Reouverture du fichier temporaire.");
   if ( (h_ftemp = fopen(nomfic, "r")) == NULL )
   {
      perror("Erreur sur fopen()");
      exit(EXIT_FAILURE);
   }

   /* Relecture des nombres aleatoires ranges dans le fichier */
   (void)puts("Relecture des nombres aleatoires ranges dans le fichier :");
   while ( fgets(buffer, TAILLE_BUF, h_ftemp) != NULL )
   {
      (void)fputs(buffer, stdout); /* Evite l'ajout d'un \n supplementaire */
   }
   
   /* Fermeture et destruction du fichier temporaire */
   if ( fclose(h_ftemp) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la fermeture du fichier par fclose()");
      exit(EXIT_FAILURE);
   }
   (void)puts("Destruction du fichier temporaire");
   if ( remove(nomfic) != EXIT_SUCCESS )
   {
      perror("Erreur lors de la destruction du fichier par remove()");
      exit(EXIT_FAILURE);
   }

   (void)puts("Fin du programme tmpnam.");
   return EXIT_SUCCESS;
}

Autres fonctions

Les systèmes genre UNIX offrent d'autres fonctions pour respectivement la création : de noms de fichiers, de fichiers et de répertoires temporaires : mktemp, mkstemp, mkdtemp.

Problèmes de sécurité possibles

Si les données à stocker dans le fichier temporaire ne doivent pas être connues hors du programme, certaines fonctions peuvent poser des problèmes de sécurité :

  • Les fichiers peuvent être créés avec des droits d'accès trop ouverts.
  • Des programmes malveillants peuvent scruter certains répertoires pour connaître les fichiers créés et piller les données contenues.
  • Certains artifices malveillants peuvent agir dans la phase située entre la création du nom de fichier et son ouverture.

Gestion de fichiers

Généralités

stdio.h fournit les prototypes de deux fonctions remove et rename qui permettent de détruire ou de renommer un fichier.

D'autres fonctions de manipulation du système de fichier sont standardisée par la norme POSIX 1003.1.

Pour la description de ces fonctions système, voir le wikilivre Programmation POSIX.

remove : suppression de fichier

La fonction remove supprime un fichier ou un répertoire vide de la liste des entrées d'un répertoire. Elle retourne 0 en cas de succès.

/*
Nom : remove.c
Auteur : Thierry46
Role : Supprime le fichier dont le nom est passé en paramètre.
Code retour : 0 (EXIT_SUCCESS), EXIT_FAILURE si probleme
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -pedantic -o remove.exe remove.c
Pour exécuter, tapez : ./remove.exe nom_fichier
Version : 1.0 du 10/4/2008
Licence : GNU GPL
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
int main(int argc, char *argv[])
{
   /* test rapide des parametres : un seul obligatoire */
   assert(argc == 2);

   /* Tente de supprimer le fichier dont le nom est passe en parametre */
   if (remove(argv[1]) == 0)
   {
      (void)printf("Le fichier %s est detruit\n", argv[1]);
   }
   else
   {
      perror(argv[1]);
      exit(EXIT_FAILURE);
   }

   return EXIT_SUCCESS;
}

rename : renommer un fichier ou un répertoire

La fonction rename permet de changer le nom d'un fichier ou d'un répertoire. Elle accepte deux chaînes de caractères en paramètre :

  • la première est le nom du fichier à renommer,
  • le deuxième est son nouveau nom.

Elle retourne 0 en cas de succès ou un code d'erreur en cas de problème.

/*
 Nom : rename.c
 Auteur : Thierry46
 Role : Renomme un fichier ou un repertoire.
 Parametres :
 - 1er : ancien nom
 - 2eme : nouveau nom
 Code retour : 0 (EXIT_SUCCESS), EXIT_FAILURE si probleme
 Pour produire un exécutable avec le compilateur libre GCC :
 gcc -Wall -pedantic -o test_rename.exe rename.c
 Pour exécuter, tapez : ./test_rename.exe source dest
 Version : 1.0 du 11/4/2008
 Licence : GNU GPL
 */

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main(int argc, char *argv[])
{
   /* test rapide des parametres : deux obligatoires */
   assert(argc == 3);
   
   /* Tente de renommer le fichier */
   if (rename(argv[1], argv[2]) == 0)
   {
      (void)printf("Le fichier %s est renomme en %s\n",
                   argv[1], argv[2]);
   }
   else
   {
      perror("rename");
      (void)fprintf(stderr,
                   "Impossible de renommer le fichier %s en %s\n",
                   argv[1], argv[2]);
      exit(EXIT_FAILURE);
   }
   
   return EXIT_SUCCESS;
}

stdlib.h

Le fichier en-tête <stdlib.h> déclare des fonctions qui effectuent la conversion de nombres, la gestion de la mémoire et d'autres tâches.

Conversion de chaînes en nombres

Certaines fonctions de <stdlib.h> permettent de la conversion de chaînes de caractères en valeurs numériques.

Les fonctions

  • int atoi(const char *CH)
atoi retourne la valeur numérique représentée par CH comme int
(depréciée par strtol).
  • long atol(const char *CH)
atol retourne la valeur numérique représentée par CH comme long
(depréciée par strtol).
  • double atof(const char *CH)
atof retourne la valeur numérique représentée par CH comme double
(depréciée par strtod).
  • long strtol(const char * restrict nptr, char ** restrict endptr, int base)
strtol retourne la chaine nptr considérées comme étant en base de numération base comme un long. Le reste de la chaine qui n'a pas pu etre convertie est pointée par *endptr, positionne errno en cas d'erreur.
  • De même strtod convertit une chaîne en double.

Remarques

  • En cas de problème de conversion, les fonctions atoi, atol, atof retournent la valeur 0 sans rien signaler. Par contre, les fonctions strtol et strtod positionnent errno en cas de problème.
  • Le programmeur peut aussi utiliser la fonction sscanf pour ce genre de conversion.

Exemple de conversion d'une chaîne en int

Le programme suivant strtol.c convertit une chaine de caractères en int avec strtol. Le traitement des erreurs de conversions est effectué en détail.

/*
Nom : strtol.c
Role : Tente de convertir le paramètre en int en utilisant strtol.
Paramètres : argv[1] : La chaine a convertir.
Code retour : 0 (EXIT_SUCCESS), autre en cas d'erreur.
Auteur : Thierry46
Pour produire un exécutable avec le compilateur libre GCC :
   gcc -Wall -pedantic -std=c99 -o strtol.exe strtol.c
Pour exécuter, tapez : ./strtol.exe
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
 
int main(int argc, char *argv[])
{
   int codeRetour = EXIT_SUCCESS;
   long resuLong;
   char *posErreur;
   int resuInt;
   
   // Contrôle rapide nombre de parametres
   assert(argc == 2);
   
   // Conversion du parametre du programme en long
   resuLong = strtol(argv[1], &posErreur, 10);

   // Traitement des erreurs
   switch (errno)
   {
      case EXIT_SUCCESS :
         // Conversion du long en int
         if (resuLong >= INT_MIN && resuLong <= INT_MAX)
         {
            resuInt = (int)resuLong;
            (void)printf("Valeur convertie avec succes en int : %d\n",
                         resuInt);         }
         else
         {
            (void)fprintf(stderr, "%s ne peut pas etre converti en \"int\" :\n"
                          "hors [%d;%d]\n",
                          argv[1], INT_MIN, INT_MAX);
            codeRetour = EXIT_FAILURE;
         }
      break;
      
      case EINVAL :
         perror(__func__);
         (void)fprintf(stderr, "%s ne contient pas une valeur numerique.\n",
                       argv[1]);
         codeRetour = EXIT_FAILURE;
      break;
      
      case ERANGE :
         perror(__func__);
         (void)fprintf(stderr, "%s ne tient pas dans un \"long\" :\n"
                       "hors [%ld;%ld]\n",
                       argv[1], LONG_MIN, LONG_MAX);
         codeRetour = EXIT_FAILURE;
      break;
         
      default :
         perror(__func__);
         codeRetour = EXIT_FAILURE;
   } // switch (errno)

   // Impression des valeurs retournees par strtol
   (void)printf("Valeur retourne par strtol : %ld, chaine non lue : \"%s\"\n",
                resuLong, posErreur);
   
   return codeRetour;
} // int main(...

Gestion de la mémoire

Pour la syntaxe des fonctions d'allocation mémoire, voir le wikilivre Programmation C, chapitre sur la gestion de la mémoire.

Principes à respecter

  • Tester le pointeur renvoyé par les fonctions d'allocation, si NULL un problème a eu lieu.
  • Une taille ou un nombre d'éléments à 0 avec malloc et calloc peuvent donner un pointeur qu'il ne faut pas utiliser.
  • Libérer la mémoire allouée dès que possible à l'aide de la fonction free(). Ne pas charger ou conserver inutilement des éléments en mémoire.
  • Les fonctions d'allocation prennent :
    • en priorité la mémoire libérée par les free() précédents
    • si pas assez de mémoire contigüe disponible au niveau du process -> demande au système un morceau de mémoire commune a tous les process par une fonction système de bas niveau (2) comme sbrk().
  • free() rend la mémoire disponible pour le process en cours, mais peut ne pas la rendre au système.
  • Ne pas mélanger les appels aux fonctions d'allocation mémoire de niveau système(2) et celles de niveau utilisateur (3) déclarées dans stdlib.h.
  • Pour éviter un morcèlement de la mémoire du process, libérer la mémoire en respectant la règle du "dernier alloué, premier libéré"
  • Eviter de morceler la mémoire en allouant et desallouant des zones de mémoire de très petites tailles.
  • Si des structures contiennent des pointeurs (Ex.: char *) et qu'elles sont de taille impaire, lors de deux allocations successives ces pointeurs peuvent être a cheval sur une frontière de mot, ce qui entraîne des cycles machines supplémentaires lors de l'accès aux données. C'est pourquoi, il serait souhaitable d'utiliser des fonction d'allocation avec contraintes d'alignement comme valloc à la place de malloc. Attention, ces fonction peuvent ne pas être portables.

A éviter

Si beaucoup d'allocation de gros objets, effondrement du système par manque de mémoire. Les autres process n'auront plus de mémoire souvent jusqu'à ce que notre process soit terminé, ce qui peut s'éterniser...

/*
boucle d'allocation memoire pour tous les objets.
A CHAQUE FOIS : DEMANDE DE MEMOIRE AU SYSTEME.
*/

for (i=1; i<n; i++)
{ ... ... malloc(...); }

/* boucle de traitement */
for (i=1; i<n; i++) { /* Traitement */ }

/*
Liberation de la memoire :
CETTE MEMOIRE NE SERA PAS RENDUE AU SYSTEME AVANT LA FIN DU PROCESS
*/
for (i=1; i<n; i++) free(...);

Exemple meilleur

Si possible, allocation et libération successive pour chaque objet.

for (i=1; i<n; i++)
{
	/* allocation */
	...malloc(...);
	/* traitement */
	...
	/* Liberation */
	free(...)
}
  • L'utilisation de tableau de type VLA Variable Lengh Array introduit par l'ISO C99, peut être une solution.

Communication avec le système

Gestion de l'arrêt du programme

La fonction principale main d'un programme prévoit habituellement un point de sortie par une instruction return suivie d'un code retour destiné au système : 0 (EXIT_SUCCESS) si le fonctionnement a été jugé nominal ou une autre valeur qui pourra être considéré par l'environnement d'appel comme un code d'erreur. Cette instruction fait appel à la fonction exit vue plus bas et donc appelle les fonctions de terminaison mises en place par atexit.

Les fonctions :

En plus de l'instruction return, la bibliothèque standard du langage C offre trois fonctions décrites dans l'include stdlib.h pour terminer un programme.

  • void abort(void)
abort termine le processus en cours de façon anormale. Il cause la génération du signal SIGABRT que le programme pourra intercepté dans le seul but de nettoyer son environnement. Le processus se terminera de toute façon. Les flux ouverts sont vidés et fermés.
  • void exit(int status)
exit termine un programme de façon normale et vide et ferme tous les flux encore ouverts. Les fonctions de terminaison empilées par atexit() sont exécutées dans l'ordre inverse de leur enregistrement. La fonction exit redonne enfin le contrôle à l'environnement du programme en retournant la valeur status comme code de retour.
  • int atexit(void (*function)(void))
atexit enregistre à chaque appel une fonction de terminaison function à exécuter en sortie de votre programme par exit ou return. La fonction exit ne doit jamais être appelée dans function. Au moins 32 fonctions de terminaison peuvent être empilées.
Ce mécanisme permet d'exécuter le code de fonctions de terminaison de façon systématique quel que soit le point de sortie emprunté. Il est utile par exemple pour éliminer un verrou placé par votre programme afin de garantir un accès exclusif à un répertoire, pour se déconnecter proprement d'une base de données... Vous devez cependant envisager les situations d'exception où votre programme se termine anormalement : par abort, par le système (signal non pris en compte ou imparable), par un arrêt brutal de l'ordinateur...

Un exemple :

/*
 *  Nom ............ : atexit.c
 *  Role ........... : Montrer l'utilisation de atexit
 *  Auteur ......... : Thierry46.
 *  Licence ........ : GNU FDL
 *  Version ........ : V1.0 du 19/02/2008
 *  Compilation .... : gcc -Wall -pedantic -std=c99 -o atexit.exe atexit.c
 */

#include <stdio.h>
#include <stdlib.h>

static void premiereFonction(void);
static void deuxiemeFonction(void);

int main(void)
{
   // Enregistrement de 2 fonctions a executer en sortie du programme
   if (atexit(premiereFonction) != 0)
   {
      perror("atexit(premiereFonction)");
      exit(EXIT_FAILURE);
   }
   if (atexit(deuxiemeFonction) != 0)
   {
      perror("atexit(deuxiemeFonction)");
      exit(EXIT_FAILURE);
   }
   
   (void)puts("Avant return");
   return EXIT_SUCCESS;
} // int main(void)

static void premiereFonction(void)
{
   (void)puts("1ere fonction");
}

static void deuxiemeFonction(void)
{
   (void)puts("2eme fonction");
}

Exécution d'une commande par system

int system(const char * string);

La fonction system permet de démarrer un processeur de commande fournit par l'environnement hôte et de lui passer une chaîne de caractère string contenant la commande à exécuter. system retourne 0 si tout s'est bien passé.

Exemple d'utilisation :

Le programme contenu dans le source system.c lance le script UNIX bonjour.sh

/*
 *  system.c
 *  Created by Thierry46 on 20/02/08.
 *  compilation : gcc -Wall -pedantic -o system.exe system.c
 */

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
   int cr;
   
   // Teste si un processeur de commande est disponible.
   cr = system(NULL);
   if (cr == 0)
   {
      (void)fputs("Pas de processeur de commande disponible sur ce systeme",
                  stderr);
      exit(EXIT_FAILURE);
   }
   
   // Demande au processeur de commande d'exécuter la commande.
   cr = system("./bonjour.sh");
   if (cr != 0)
   {
      (void)fprintf(stderr,
                    "Code erreur %d retourne par system\n",
                    cr);
   }
   
   return EXIT_SUCCESS;
}

Le script bash bonjour.sh écrit la chaîne "Bonjour tout le monde !" sur la sortie standard stdout.

#! /bin/sh
echo "Bonjour tout le monde !"
exit 0;

Remarques portabilité :

  • Il faut tester si un processeur de commande est disponible (voir l'exemple).
  • La portabilité de la commande est limitée : le processeur de commande dépend du système hôte, les codes retours dépendent de l'implémentation.
  • C'est un moyen coûteux en ressource : pour de nombreuses tâches comme afficher la liste des fichiers d'un répertoire par exemple, il vaut mieux appeler les fonctions systèmes comme stat(2).

Lecture et écriture dans l'environnement

Il est possible de récupérer la valeur d'une variable de l'environnement hôte avec la fonction getenv :

char *getenv(const char *name);

Le paramètre name est une chaîne de caractère qui représente le nom de la variable à demander à l'environnement hôte. Si la variable demandée existe, un pointeur sur la zone mémoire la contenant est retourné, sinon NULL.

Exemple d'utilisation :

Le programme contenu dans le source getenv.c retourne la valeur d'une variable d'environnement dont le nom lui est passé en paramètre :

/*
Nom : getenv.c
Auteur : Thierry46
Role : affiche à l'écran une variable d'environnement recuperee avec getenv
Paramètres : Le nom de la variable à récupérer.
Code retour : 0 (EXIT_SUCCESS) si la variable existe, EXIT_FAILURE sinon
Produire un binaire  : gcc -Wall -Wall -pedantic -std=c99 -o getenv.exe getenv.c
Pour exécuter, tapez : ./getenv.exe

Version : 1.0 du 22/2/2008
Licence : GNU GPL
*/

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[])
{
   char const * valeurVar = NULL;
   int codeRetour = EXIT_SUCCESS;
   
   // Test parametre
   if (argc != 2)
   {
      (void)fprintf(stderr,
                    "usage : %s variable\n"
                    "variable est le nom d'une variable d'environnement a afficher\n",
                    argv[0]);
      exit(EXIT_FAILURE);
   }
   
   // Recuperation valeur
   valeurVar = getenv(argv[1]);
   
   // Affichage valeur
   if (valeurVar == NULL)
   {
      (void)printf("La variable %s n'est pas definie dans l'environnement hote\n",
                   argv[1]);
      codeRetour = EXIT_FAILURE; 
   }
   else
   {
      (void)printf("La variable %s vaut : %s\n",
                   argv[1], valeurVar);
   }

   return codeRetour;
} // int main(...

Remarques sur l'utilisation de getenv :

  • Les variables d'environnement prédéfinies dépendent souvent du système hôte : Windows, Unix, Linux, il faut absolument tester si le retour de getenv est NULL.
  • Il est interdit de modifier la chaîne retournée par getenv.
  • Il faut sauvegarder dans une autre zone mémoire ou utiliser rapidement la valeur retournée. Un appel ultérieur à getenv modifiera ces valeurs.
  • L'utilisation des variables d'environnement est cependant très utile pour assurer la w:traçabilité dans les logiciels (nom de l'utilisateur, du système utilisé, de la machine d'exécution...) dans des fichiers de traces (log) ou sur les sorties (impressions, entête de fichiers de sortie).

Pour positionner une variable accessible dans l'environnement du programme, il existe les fonctions setenv et putenv.

Algorithmes

Tri d'un tableau d'éléments

void qsort(void *base, size_t nmemb, size_t size,
         int (*compar)(const void *, const void *));

La fonction qsort (quick sort = tri rapide) permet de trier un tableau contenant des données de toutes natures dans un ordre croissant. Elle appelle votre fonction de comparaison compar en lui passant des pointeurs sur deux éléments du tableau.

Votre fonction compar doit déterminer si les deux éléments sont dans le bon ordre. Si vous estimez que :

  • le premier est plus petit (inférieur) au deuxième, vous renvoyez une valeur entière négative (inférieure à 0). Cela signifie aussi que les deux éléments sont dans le bon ordre.
  • le premier est égal au deuxième, vous renvoyez une valeur entière égale à 0. Ce test d'égalité peut servir pour une utilisation ultérieure de compar avec une fonction de recherche comme bsearch.
  • le premier est plus grand (supérieur) au deuxième, vous renvoyez une valeur entière positive (supérieur à 0). Cela signifie aussi que les deux éléments ne sont pas dans le bon ordre.

Exemple :

/***************************************************************************
Nom ......... : qsort1.c
Role ........ : Tri d'un tableau de structure dans l'ordre croissant
                du champ valeur. 
Auteur ...... : Thierry46
Date creation : 10/2/2008
Compilation : gcc -Wall -pedantic -std=c99 -o test_qsort1 qsort1.c
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// Definition d'un element de structure
typedef struct
{
   unsigned int rang;
   int valeur;
} ELEMENT;

// Prototype de la fonction de tri appelee par qsort
static int qs_tri(const void *ptr1, const void *ptr2);

int main(void)
{
   ELEMENT tableau[] = { {1, 5}, {2, 5}, {3, 3}, {4, 12}, {5, 1} };
   size_t i;
   size_t nb_element = sizeof(tableau)/sizeof(tableau[0]);

   // appel fonction de tri qsort (stdlib.h)
   qsort(tableau, nb_element, sizeof(ELEMENT), qs_tri);
   
   // Impression Apres le tri
   (void)puts("Apres le tri :");
   for (i=0 ; i<nb_element; i++)
   {
      (void)printf("tableau[%zu] = (%u, %d)\n",
                   i, tableau[i].rang, tableau[i].valeur);
   }
   return EXIT_SUCCESS;
} // int main(void)

/***************************
Fonction .... : qs_tri
Role ........ : Fonction de comparaison de deux element
                appelée par qsort()
Parametres .. :
- ptr1 : pointeur sur le 1er element
- ptr2 : pointeur sur le 2ème element
Retour ........ :
    <0 : le premier element est plus petit que le deuxième,
         ils sont dans le bon ordre; 
    =0 : le premier element est égal au deuxième
    <0 : le premier element est plus grand que le deuxième,
         ils ne sont pas dans l'ordre.
****************************/
static int qs_tri(const void *ptr1, const void *ptr2)
{   
   int valeur1 = ((ELEMENT *)ptr1)->valeur;
   int valeur2 = ((ELEMENT *)ptr2)->valeur;
   
   return valeur1 - valeur2;
} // static int qs_tri(...

Résultats :

MacMini-TM:~/Documents/exemples_c thierry$ ./test_qsort1
Apres le tri :
tableau[0] = (5, 1)
tableau[1] = (3, 3)
tableau[2] = (1, 5)
tableau[3] = (2, 5)
tableau[4] = (4, 12)

Recherche dans un tableau trié

void * bsearch(const void *key, const void *base,
         size_t nmemb, size_t size,
         int (*compar) (const void *, const void *));

La fonction bsearch (binary search = recherche dichotomique) permet de rechercher si un tableau trie suivant la fonction de comparaison compar contient l'élément *key (selon les critères définis dans la fonction compar).

Exemple basé sur le précédent :

// Ajout dans les déclarations
   ELEMENT cle = {0, 3};
   ELEMENT *resultat;

// Ajout en fin de la fonction main
   resultat = bsearch(&cle, tableau, nb_element, sizeof(ELEMENT), qs_tri);
   if (resultat != NULL)
   {
      (void)printf("resultat = (%u, %d)\n",
                   resultat->rang, resultat->valeur);
   }
   else
   {
      (void)puts("Pas trouve");
   }

Résultats

...
resultat = (3, 3)

L'élément {0, 3} a été trouvé par bsearch qui a renvoyé (3, 3) car la fonction qs_tri de l'exemple ne compare que le champ valeur de la structure ELEMENT.

Génération de nombres pseudo-aléatoires

Le langage C fournit plusieurs fonctions générant de séries de nombres pseudo-aléatoires. Les séries sont reproductibles à condition de démarrer le générateur à partir de la même graine (seed en anglais).

La fonction rand et son modificateur de graine srand produisent des entiers dans l'intervalle [0;RAND_MAX]. RAND_MAX est une constante de stdlib.h valant au moins 32767.

int rand(void);
void srand(unsigned seed);

Fonctionnement :

On appelle éventuellement une fois srand en lui passant la graine, puis les appels successifs de rand retournent les nombres de la série.

Remarques

  • On peut choisir une série de façon aléatoire en fournissant comme paramètre à srand le retour de la fonction time de <time.h> : srand((unsigned int)time((time_t *)NULL));.
  • Le générateur random et son modificateur de graine srandom sont meilleurs que rand (moins cyclique).

Fonctions non conformes ANSI-C

Les trois fonctions suivantes sont réservées à DOS: elles ne sont pas conformes au standard ANSI-C et elles ne sont pas portables. Conversion de nombres en chaînes de caractères

  • char *itoa (int VAL, char *CH, int B)
itoa convertit VAL dans une chaîne de caractères terminée par '\0' et attribue le résultat à CH; itoa retourne CH comme résultat. B est la base utilisée pour la conversion de VAL. B doit être compris entre 2 et 36 (inclus). (Réservez assez de mémoire pour la chaîne résultat: itoa peut retourner jusqu'à 17 bytes.)
  • char *ltoa (long VAL, char *CH, int B)
ltoa convertit VAL dans une chaîne de caractères terminée par '\0' et attribue le résultat à CH; ltoa retourne CH comme résultat. B est la base utilisée pour la conversion de VAL. B doit être compris entre 2 et 36 (inclus). (ltoa peut retourner jusqu'à 33 bytes.)
  • char *ultoa (unsigned long VAL, char *CH, int B)

ultoa convertit VAL dans une chaîne de caractères terminée par '\0' et attribue le résultat à CH; ultoa retourne CH comme résultat. B est la base utilisée pour la conversion de VAL. B doit être compris entre 2 et 36 (inclus). (ultoa peut retourner jusqu'à 33 bytes.)

string.h

Les fonctions déclarées dans string.h permettent de manipuler les chaînes de caractères.

Les fonctions

Pour une description détaillée des fonctions de manipulation des chaînes de caractères, voir aussi dans le WikiLivre Programmation C, chapitre Chaînes de caractères.

Les risques

  • Les chaines de caractères en C ne sont rien d'autres que des tableaux de caractères stockées dans une zone mémoire. Elles doivent normalement se terminer par un caractère nul : '\0'. Les fonctions comme strcpy attendent ce code. Sans lui, elles accèdent aux données situées hors de la zone de stockage. Ce qui est souvent fatal ou peut conduire à une erreur se produisant beaucoup plus loin.
Il faut utiliser de préférence les fonctions qui permettent de définir une taille maximale pour la chaine à traiter comme strncpy, strncmp...
  • Les chaines allouées par le compilateur ne doivent être utilisées qu'en lecture seulement.

time.h

Présentation

Les premiers développeurs d'UNIX accordaient en grande importances aux calcul de dates et d'heures. Les fonctions déclarées dans time.h sont donc nombreuses.

La norme C laisse beaucoup de liberté quant à l'implémentation des fonctions de date. Pour les programmes qui nécessitent une grande précision, il faudra faire des tests pour évaluer le système cible sur ce point.

Type de données propres à time.h

  • clock_t est un type entier permettant de représenter le temps écoulé depuis le démarrage du programme.
  • time_t représente un entier signé positif après l'Epoch (1er janvier 1970 à 0 heure, 0 minute, 0 seconde sous UNIX) et négatif avant. Le code retour de la fonction en cas d'erreur est (time_t)-1. Cette valeur -1 représente aussi la dernière seconde de l'année 1969.

La structure tm

La structure tm permet de stocker un temps décomposé dans ses différents champs :

  • int tm_sec : nombre de secondes (0 - 60), la seconde supplémentaire est nécessaire pour des recalages.
  • int tm_min : minutes (0 - 59)
  • int tm_hour : heures (0 - 23)
  • int tm_mday : numéro du jour dans le mois (1 - 31)
  • int tm_mon : numéro du mois dans l'année (0 - 11)
  • int tm_year : année - 1900
  • int tm_wday : numéro du jour dans la semaine (Dimanche = 0)
  • int tm_yday : numéro du jour dans l'année (0 - 365)
  • int tm_isdst : différent de 0 si l'heure d'été en vigueur
  • char *tm_zone : abréviation d'une zone géographique concernant le temps. Exemple : CET pour Central European Time = UTC + 1 heure [2].
  • long tm_gmtoff : décalage du temps UTC en secondes par rapport au 1er méridien.

Temps écoulé

Fonction clock()

La fonction clock_t clock(void) permet de connaître le nombre de fractions de temps machine écoulées depuis le début du programme. La valeur retournée peut être convertie en secondes en la divisant par la constante CLOCKS_PER_SEC. Elle peut servir à évaluer l'espace de temps entre deux événements.

Un exemple d'utilisation :

/***************************************************************************
Nom ......... : clock.c
Role ........ : Recuperation du temps passe dans le programme. 
Auteur ...... : Thierry46
Date Modif .. : 27/4/2008
Compilation : gcc -Wall -o test_clock clock.c
Exemple d'utilisation : ./test_clock
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
   long clk_tck = CLOCKS_PER_SEC;
   clock_t t1, t2;
   int i;
   int a = 0;

   /* Recuperation du temps initial en "clock ticks" */
   t1 = clock();

   /* realisation du calcul */
   for ( i = 0; i < 10000000; i++) {a++;}
   (void)printf("a = %d\n", a);
   
   /* Recuperation du temps final en "clock ticks" */
   t2 = clock();

   /* Affichage des differents temps et temps consommes */
   (void)printf("Nb ticks/seconde = %ld,  Nb ticks depart : %ld, "
                "Nb ticks final : %ld\n",
                clk_tck, (long)t1, (long)t2);
   (void)printf("Temps consomme (s) : %lf \n",
                (double)(t2-t1)/(double)clk_tck);
      
   return EXIT_SUCCESS;
}

Donne à l'exécution sur mon système :

MacMini-TM:~/Documents/developpement/c thierry$ ./test_clock
a = 10000000
Nb ticks/seconde = 100,  Nb ticks depart : 0, Nb ticks final : 4
Temps consomme (s) : 0.040000 

Remarque : temps user et système

Pour obtenir des données comme le temps consommé par vos calculs (temps user), le temps utilisé lors des appels au système (temps système) et bien d'autres informations comme le fait la commande time (1) d'un système UNIX, vous pouvez utiliser la fonction UNIX getrusage. Cette fonction ne fait pas partie de time.h :

#include <sys/time.h>
#include <sys/resource.h>
/*...*/
   ret = getrusage(RUSAGE_SELF, &usage);
   if (ret == 0)
   {
      double tempsSystem = (double)usage.ru_stime.tv_sec +
         (int)usage.ru_stime.tv_usec * 1E-6;
      double tempsUser = (double)usage.ru_utime.tv_sec +
         (int)usage.ru_utime.tv_usec * 1E-6;
      (void)printf("system: %lf user: %lf\n"
             "changements de contexte involontaires : %ld\n",
             tempsSystem,
             tempsUser,
             usage.ru_nivcsw);
   }
/*...*/

Date, heure et calendrier

Fonction time()

La fonction time_t time(time_t *tloc) retourne le nombre de secondes écoulées depuis le 1er janvier 1970 à 0 heure, 0 minute, 0 seconde sous UNIX, UTC.

Un exemple :

/***************************************************************************
Nom ......... : temps2.c
Role ........ : Fonctions de recuperation du temps. 
Auteur ...... : Thierry46
Date ........ : 28/4/2008
Compilation : gcc -Wall -o temps2.exe temps2.c
Exemple d'utilisation : ./temps2.exe
***************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
   time_t t1;

   t1 = time(NULL);
   if (t1 == (time_t)-1)
   {
      perror("time");
      exit(EXIT_FAILURE);
   }
   (void)printf("%ld secondes, %s\n", t1, ctime(&t1));
   return EXIT_SUCCESS;
}

Donne à l'exécution sur mon système :

MacMini-TM:~/Documents/developpement/c thierry$ ./temps2.exe
1209413960 secondes, Mon Apr 28 22:19:20 2008

Autres fonctions

  • double difftime(time_t time1, time_t time0) Retourne le nombre de secondes qui séparent les temps time1 et time0.
  • time_t mktime(struct tm *tm) Cette fonction accepte en paramètre l'adresse d'une structure tm dont vous aurez compléter les champs. La fonction normalisera les champs et renseignera les champs tm_wday, tm_yday et tm_mday si tm_mon et tm_year sont connus. Elle retourne aussi le nombre de secondes depuis l'Epoch.
  • char *asctime(const struct tm *tm) Convertit le temps décomposé tm en une chaîne de caractères du genre Mon Apr 28 22:19:20 2008.
  • struct tm *localtime(const time_t *clock) Convertit le temps en secondes clock en un temps décomposé exprimé en temps local dans la structure tm.
  • struct tm *gmtime(const time_t *clock) Convertit le temps en secondes clock en un temps décomposé exprimé en temps UTC dans la structure tm.
  • char *ctime(const time_t *clock) = asctime(localtime(clock)).
  • size_t strftime(char * restrict buf, size_t maxsize, const char * restrict format, const struct tm * restrict timeptr) convertit dans la chaîne buf, la date décomposée tm selon les directives indiquées par format.

conio.h (spécifique MS-DOS)

La fonction suivante est réservée à DOS: elle n'est pas conforme au standard ANSI-C et elle n'est pas portable.

  • int getch(void)
getch lit un seul caractère au clavier et le retourne comme résultat sans l'écrire sur l'écran et sans attendre un retour à la ligne.

Sous Linux, un équivalent (sans conio.h) pourrais être :

int mygetch(void)
{
    struct termios oldt, newt;
    int ch;

    tcgetattr( STDIN_FILENO, &oldt );
    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );
    ch = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
    return ch;
}

TP

Faîtes les exercices du WikiLivre Exercices en langage C sur les fonctions standards.

Références et liens externes

  1. (en) Site cplusplus.com Parallèle entre les fonctions standards du C et du C++ et explications.
  2. (en)liste des abréviations pour les zones de temps