Leçons de niveau 15

Fonctions de base en langage C/stdlib.h

Une page de Wikiversité.
Sauter à la navigation Sauter à la recherche
Début de la boite de navigation du chapitre
stdlib.h
Icône de la faculté
Chapitre no 6
Leçon : Fonctions de base en langage C
Chap. préc. :stdio.h
Chap. suiv. :string.h
fin de la boite de navigation du chapitre
Icon falscher Titel.svg
En raison de limitations techniques, la typographie souhaitable du titre, « Fonctions de base en langage C : stdlib.h
Fonctions de base en langage C/stdlib.h
 », n'a pu être restituée correctement ci-dessus.

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[modifier | modifier le wikicode]

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

Les fonctions[modifier | modifier le wikicode]

  • 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 être convertie est pointée par *endptr, positionne errno en cas d'erreur.
  • De même strtod convertit une chaîne en double.

Remarques[modifier | modifier le wikicode]

  • 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[modifier | modifier le wikicode]

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 succès en int : %d\n",
                         resuInt);         }
         else
         {
            (void)fprintf(stderr, "%s ne peut pas être 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[modifier | modifier le wikicode]

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

Principes à respecter[modifier | modifier le wikicode]

  • 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.

À éviter[modifier | modifier le wikicode]

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.
À 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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

Gestion de l'arrêt du programme[modifier | modifier le wikicode]

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 exécuter 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[modifier | modifier le wikicode]

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 système",
                  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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

Tri d'un tableau d'éléments[modifier | modifier le wikicode]

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 Après le tri
   (void)puts("Après 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 1{{er}} element
- ptr2 : pointeur sur le 2{{e}} 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
Après 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é[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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[modifier | modifier le wikicode]

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.)