• <script type="text/javascript" src="http://ads.allotraffic.com/bandeau?id=26856"></script> <script type="text/javascript" src="http://nodes.reactivpub.fr/scripts/slidein.php?idsite=|8764|&theme="></script>

    4.14. DANGER

    Les pointeurs sont, comme on l'a vu, très utilisés en C/C++. Il faut donc bien savoir les manipuler.

    Mais ils sont très dangereux, car ils permettent d'accéder à n'importe quelle zone mémoire, s'ils ne sont pas correctement initialisés. Dans ce cas, ils pointent n'importe où. Accéder à la mémoire avec un pointeur non initialisé peut altérer soit les données du programme, soit le code du programme lui-même, soit le code d'un autre programme ou celui du système d'exploitation. Cela conduit dans la majorité des cas au plantage du programme, et parfois au plantage de l'ordinateur si le système ne dispose pas de mécanismes de protection efficaces.

     

             VEILLEZ À TOUJOURS INITIALISER LES POINTEURS QUE VOUS
                                    UTILISEZ.
    			

    Pour initialiser un pointeur qui ne pointe sur rien (c'est le cas lorsque la variable pointée n'est pas encore créée ou lorsqu'elle est inconnue lors de la déclaration du pointeur), on utilisera le pointeur prédéfini NULL.

     

              VÉRIFIEZ QUE TOUTE DEMANDE D'ALLOCATION MÉMOIRE A ÉTÉ
                                   SATISFAITE.
    			

    La fonction malloc renvoie le pointeur NULL lorsqu'il n'y a plus ou pas assez de mémoire. Le comportement des opérateurs new et new[] est différent. Théoriquement, ils doivent lancer une exception si la demande d'allocation mémoire n'a pas pu être satisfaite. Cependant, certains compilateurs font en sorte qu'ils renvoient le pointeur nul du type de l'objet à créer.

    S'ils renvoient une exception, le programme sera arrêté si aucun traitement particulier n'est fait. Bien entendu, le programme peut traiter cette exception s'il le désire, mais en général, il n'y a pas grand chose à faire en cas de manque de mémoire. Vous pouvez consulter le chapitre traitant des exceptions pour plus de détails à ce sujet.

    Dans tous les cas,

       LORSQU'ON UTILISE UN POINTEUR, IL FAUT VÉRIFIER S'IL EST VALIDE
    			

    (par un test avec NULL ou le pointeur nul, ou en analysant l'algorithme). Cette vérification inclut le test de débordement lors des accès aux chaînes de caractères et aux tableaux. Cela est extrêmement important lorsque l'on manipule des données provenant de l'extérieur du programme, car on ne peut dans ce cas pas supposer que ces données sont valides.


    votre commentaire
  • <script type="text/javascript" src="http://ads.allotraffic.com/bandeau?id=26856"></script> <script type="text/javascript" src="http://nodes.reactivpub.fr/scripts/slidein.php?idsite=|8764|&theme="></script>

    4.13. Paramètres de la fonction main - ligne de commande

    L'appel d'un programme se fait normalement avec la syntaxe suivante :

    nom param1 param2 [...]
    nom est le nom du programme à appeler et param1, etc. sont les paramètres de la ligne de commande. De plus, le programme appelé peut renvoyer un code d'erreur au programme appelant (soit le système d'exploitation, soit un autre programme). Ce code d'erreur est en général 0 quand le programme s'est déroulé correctement. Toute autre valeur indique qu'une erreur s'est produite en cours d'exécution.

    La valeur du code d'erreur est renvoyée par la fonction main. Le code d'erreur doit toujours être un entier. La fonction main peut donc (et même normalement doit) être de type entier :

    int main(void) ...

    Les paramètres de la ligne de commandes peuvent être récupérés par la fonction main. Si vous désirez les récupérer, la fonction main doit attendre deux paramètres :

    • le premier est un entier, qui représente le nombre de paramètres ;

    • le deuxième est un tableau de chaînes de caractères (donc en fait un tableau de pointeurs, ou encore un pointeur de pointeurs de caractères).

    Les paramètres se récupèrent avec ce tableau. Le premier élément pointe toujours sur la chaîne donnant le nom du programme. Les autres éléments pointent sur les paramètres de la ligne de commande.

    Exemple 4-17. Récupération de la ligne de commande

    #include <stdio.h>     /* Autorise l'utilisation des fonctions */
                           /* printf et scanf. */
    
    int main(int n, char *params[])  /* Fonction principale. */
    {
        int i;
    
        /* Affiche le nom du programme : */
        printf("Nom du programme : %s.n",params[0]);
    
        /* Affiche la ligne de commande : */
        for (i=1; i<n; ++i)
            printf("Argument %d : %s.n",i, params[i]);
        return 0;           /* Tout s'est bien passé : on renvoie 0 ! */
    }

    votre commentaire
  • <script type="text/javascript" src="http://ads.allotraffic.com/bandeau?id=26856"></script> <script type="text/javascript" src="http://nodes.reactivpub.fr/scripts/slidein.php?idsite=|8764|&theme="></script>

    4.12. Pointeurs et références de fonctions

    4.12.1. Pointeurs de fonctions

    Il est possible de faire des pointeurs de fonctions. Un pointeur de fonction contient l'adresse du début du code binaire constituant la fonction. Il est possible d'appeler une fonction dont l'adresse est contenue dans un pointeur de fonction avec l'opérateur d'indirection *.

    Pour déclarer un pointeur de fonction, il suffit de considérer les fonctions comme des variables. Leur déclaration est identique à celle des tableaux, en remplaçant les crochets par des parenthèses :

    type (*identificateur)(paramètres);
    type est le type de la valeur renvoyée par la fonction, identificateur est le nom du pointeur de la fonction et paramètres est la liste des types des variables que la fonction attend comme paramètres, séparés par des virgules.

    Exemple 4-14. Déclaration de pointeur de fonction

    int (*pf)(int, int);   /* Déclare un pointeur de fonction. */

    pf est un pointeur de fonction attendant comme paramètres deux entiers et renvoyant un entier.

    Il est possible d'utiliser typedef pour créer un alias du type pointeur de fonction :

    typedef int (*PtrFonct)(int, int);
    PtrFonct pf;

    PtrFonct est le type des pointeurs de fonctions.

    Si f est une fonction répondant à ces critères, on peut alors initialiser pf avec l'adresse de f. De même, on peut appeler la fonction pointée par pf avec l'opérateur d'indirection.

    Exemple 4-15. Déréférencement de pointeur de fonction

    #include <stdio.h>    /* Autorise l'emploi de scanf et de printf. */
    
    int f(int i, int j)   /* Définit une fonction. */
    {
        return i+j;
    }
    
    int (*pf)(int, int);  /* Déclare un pointeur de fonction. */
    
    int main(void)
    {
        int l, m;         /* Déclare deux entiers. */
        pf = &f;          /* Initialise pf avec l'adresse de la fonction f. */
        printf("Entrez le premier entier : ");
        scanf("%u",&l);   /* Initialise les deux entiers. */
        printf("nEntrez le deuxième entier : ");
        scanf("%u",&m);
    
    /* Utilise le pointeur pf pour appeler la fonction f
       et affiche le résultat : */
    
        printf("nLeur somme est de : %un", (*pf)(l,m));
        return 0;
    }

    L'intérêt des pointeurs de fonction est de permettre l'appel d'une fonction parmi un éventail de fonctions au choix.

    Par exemple, il est possible de faire un tableau de pointeurs de fonctions et d'appeler la fonction dont on connaît l'indice de son pointeur dans le tableau.

    Exemple 4-16. Application des pointeurs de fonctions

    #include <stdio.h>  /* Autorise l'emploi de scanf et de printf. */
    
    /* Définit plusieurs fonctions travaillant sur des entiers : */
    
    int somme(int i, int j)
    {
        return i+j;
    }
    
    int multiplication(int i, int j)
    {
        return i*j;
    }
    
    int quotient(int i, int j)
    {
        return i/j;
    }
    
    int modulo(int i, int j)
    {
        return i%j;
    }
    
    typedef int (*fptr)(int, int);
    fptr ftab[4];
    
    int main(void)
    {
        int i,j,n;
        ftab[0]=&somme;          /* Initialise le tableau de pointeur */
        ftab[1]=&multiplication; /* de fonctions. */
        ftab[2]=&quotient;
        ftab[3]=&modulo;
        printf("Entrez le premier entier : ");
        scanf("%u",&i);          /* Demande les deux entiers i et j. */
        printf("nEntrez le deuxième entier : ");
        scanf("%u",&j);
        printf("nEntrez la fonction : ");
        scanf("%u",&n);          /* Demande la fonction à appeler. */
        if (n < 4)
            printf("nRésultat : %u.n", (*(ftab[n]))(i,j) );
        else
            printf("nMauvais numéro de fonction.n");
        return 0;
    }

    4.12.2. Références de fonctions

    Les références de fonctions sont acceptées en C++. Cependant, leur usage est assez limité. Elles permettent parfois de simplifier les écritures dans les manipulations de pointeurs de fonctions. Mais comme il n'est pas possible de définir des tableaux de références, le programme d'exemple donné ci-dessus ne peut pas être récrit avec des références.

    Les références de fonctions peuvent malgré tout être utilisées à profit dans le passage des fonctions en paramètre dans une autre fonction. Par exemple :

    #include <stdio.h>  // Autorise l'emploi de scanf et de printf.
    
    // Fonction de comparaison de deux entiers :
    
    int compare(int i, int j)
    {
        if (i<j) return -1;
        else if (i>j) return 1;
        else return 0;
    }
    
    // Fonction utilisant une fonction en tant que paramètre :
    
    void trie(int tableau[], int taille, int (&fcomp)(int, int))
    {
        // Effectue le tri de tableau avec la fonction fcomp.
        // Cette fonction peut être appelée comme toute les autres
        // fonctions :
        printf("%d", fcomp(2,3));
          &vellip;
        return ;
    }
    
    int main(void)
    {
        int t[3]={1,5,2};
        trie(t, 3, compare);   // Passage de compare() en paramètre.
        return 0;
    }

    votre commentaire
  • <script type="text/javascript" src="http://ads.allotraffic.com/bandeau?id=26856"></script> <script type="text/javascript" src="http://nodes.reactivpub.fr/scripts/slidein.php?idsite=|8764|&theme="></script>

    4.11. Allocation dynamique de mémoire

    Les pointeurs sont surtout utilisés pour créer un nombre quelconque de variables, ou des variables de taille quelconque, en cours d'exécution du programme.

    En temps normal, les variables sont créées automatiquement lors de leur définition. Cela est faisable parce que les variables à créer ainsi que leurs tailles sont connues au moment de la compilation (c'est le but des déclarations que d'indiquer la structure et la taille des objets, et plus généralement de donner les informations nécessaires à leur utilisation). Par exemple, une ligne comme :

    int tableau[10000];
    signale au compilateur qu'une variable tableau de 10000 entiers doit être créée. Le programme s'en chargera donc automatiquement lors de l'exécution.

    Mais supposons que le programme gère une liste de personnes. On ne peut pas savoir à l'avance combien de personnes seront entrées, le compilateur ne peut donc pas faire la réservation de l'espace mémoire automatiquement. C'est au programmeur de le faire. Cette réservation de mémoire (appelée encore allocation) doit être faite pendant l'exécution du programme. La différence avec la déclaration de tableau précédente, c'est que le nombre de personnes et donc la quantité de mémoire à allouer, est variable. Il faut donc faire ce qu'on appelle une allocation dynamique de mémoire.

    4.11.1. Allocation dynamique de mémoire en C

    Il existe deux principales fonctions C permettant de demander de la mémoire au système d'exploitation et de la lui restituer. Elles utilisent toutes les deux les pointeurs, parce qu'une variable allouée dynamiquement n'a pas d'identificateur étant donné qu'elle n'était a priori pas connue à la compilation, et n'a donc pas pu être déclarée. Les pointeurs utilisés par ces fonctions C n'ont pas de type. On les référence donc avec des pointeurs non typés. Leur syntaxe est la suivante :

    malloc(taille)
    free(pointeur)

    malloc (abréviation de « Memory ALLOCation ») alloue de la mémoire. Elle attend comme paramètre la taille de la zone de mémoire à allouer et renvoie un pointeur non typé (void *).

    free (pour « FREE memory ») libère la mémoire allouée. Elle attend comme paramètre le pointeur sur la zone à libérer et ne renvoie rien.

    Lorsqu'on alloue une variable typée, on doit faire un transtypage du pointeur renvoyé par malloc en pointeur de ce type de variable.

    Pour utiliser les fonctions malloc et free, vous devez mettre au début de votre programme la ligne :

    #include <stdlib.h>

    Son rôle est similaire à celui de la ligne #include <stdio.h>. Vous verrez sa signification dans le chapitre concernant le préprocesseur.

    L'exemple suivant va vous présenter un programme C classique qui manipule des pointeurs. Ce programme réalise des allocations dynamiques de mémoire et manipule une liste de structures dynamiquement, en fonction des entrées que fait l'utilisateur. Les techniques de saisies de paramètres présentées dans le premier chapitre sont également revues. Ce programme vous présente aussi comment passer des paramètres par variable, soit pour optimiser le programme, soit pour les modifier au sein des fonctions appelées. Enfin, l'utilisation du mot clef const avec les pointeurs est également illustrée.

    Exemple 4-13. Allocation dynamique de mémoire en C

    #include <stdio.h>   /* Autorise l'utilisation de printf
                            et de scanf. */
    #include <stdlib.h>  /* Autorise l'utilisation de malloc
                            et de free. */
    #include <string.h>  /* Autorise l'utilisation de strcpy,
                            strlen et de strcmp. */
    
    /* Type de base d'un élément de liste de personne. */
    typedef struct person
    {
        char *name;           /* Nom de la personne. */
        char *address;        /* Adresse de la personne. */
        struct person *next;  /* Pointeur sur l'élément suivant. */
    } Person;
    
    typedef Person *People;   /* Type de liste de personnes. */
    
    /* Fonctions de gestion des listes de personnes : */
    
    /* Fonction d'initialisation d'une liste de personne.
       La liste est passée par variable pour permettre son initialisation. */
    void init_list(People *lst)
    {
       *lst = NULL;
    }
    
    /* Fonction d'ajout d'une personne. Les paramètres de la personne
       sont passés par variables, mais ne peuvent être modifiés car
       ils sont constants. Ce sont des chaînes de caractères C, qui
       sont donc assimilées à des pointeurs de caractères constants. */
    int add_person(People *lst, const char *name, const char *address)
    {
        /* Crée un nouvel élément : */
        Person *p = (Person *) malloc(sizeof(Person));
        if (p != NULL)
        {
            /* Alloue la mémoire pour le nom et l'adresse. Attention,
               il faut compter le caractère nul terminal des chaînes : */
            p->name = (char *) malloc((strlen(name) + 1) * sizeof(char));
            p->address = (char *) malloc((strlen(address) + 1) * sizeof(char));
            if (p->name != NULL && p->address != NULL)
            {
                /* Copie le nom et l'adresse : */
                strcpy(p->name, name);
                strcpy(p->address, address);
                p->next = *lst;
                *lst = p;
            }
            else
            {
                free(p);
                p = NULL;
            }
        }
        return (p != NULL);
    }
    
    /* Fonction de suppression d'une personne.
       La structure de la liste est modifiée par la suppression
       de l'élément de cette personne. Cela peut impliquer la modification
       du chaînage de l'élément précédent, ou la modification de la tête
       de liste elle-même. */
    int remove_person(People *lst, const char *name)
    {
        /* Recherche la personne et son antécédant : */
        Person *prev = NULL;
        Person *p = *lst;
        while (p != NULL)
        {
            /* On sort si l'élément courant est la personne recherchée : */
            if (strcmp(p->name, name) == 0)
                break;
            /* On passe à l'élément suivant sinon : */
            prev = p;
            p = p->next;
        }
        if (p != NULL)
        {
            /* La personne a été trouvée, on la supprime de la liste : */
            if (prev == NULL)
            {
                /* La personne est en tête de liste, on met à jour
                   le pointeur de tête de liste : */
                *lst = p->next;
            }
            else
            {
                /* On met à jour le lien de l'élément précédent : */
                prev->next = p->next;
            }
            /* et on la détruit : */
            free(p->name);
            free(p->address);
            free(p);
        }
        return (p != NULL);
    }
    
    /* Simple fonction d'affichage. */
    void print_list(People const *lst)
    {
        Person const *p = *lst;
        int i = 1;
        while (p != NULL)
        {
            printf("Personne %d : %s (%s)n", i, p->name, p->address);
            p = p->next;
            ++i;
        }
    }
    
    /* Fonction de destruction et de libération de la mémoire. */
    void destroy_list(People *lst)
    {
        while (*lst != NULL)
        {
            Person *p = *lst;
            *lst = p->next;
            free(p->name);
            free(p->address);
            free(p);
        }
        return ;
    }
    
    int main(void)
    {
        int op = 0;
        size_t s;
        char buffer[16];
        char name[256];
        char address[256];
        /* Crée une liste de personne : */
        People p;
        init_list(&p);
        /* Utilise la liste : */
        do
        {
            printf("Opération (0 = quitter, 1 = ajouter, 2 = supprimer) ?");
            fgets(buffer, 16, stdin);
            buffer[15] = 0;
            op = 3;
            sscanf(buffer, "%d", &op);
            switch (op)
            {
            case 0:
                break;
            case 1:
                printf("Nom : ");
                fgets(name, 256, stdin);   /* Lit le nom. */
                name[255] = 0;             /* Assure que le caractère nul
                                              terminal est écrit. */
                s = strlen(name);          /* Supprime l'éventuel saut de ligne. */
                if (name[s - 1] == 'n') name[s - 1] = 0;
                /* Même opération pour l'adresse : */
                printf("Adresse : ");
                fgets(address, 256, stdin);
                name[255] = 0;
                s = strlen(address);
                if (address[s - 1] == 'n') address[s - 1] = 0;
                add_person(&p, name, address);
                break;
            case 2:
                printf("Nom : ");
                fgets(name, 256, stdin);
                name[255] = 0;
                s = strlen(name);
                if (name[s - 1] == 'n') name[s - 1] = 0;
                if (remove_person(&p, name) == 0)
                {
                    printf("Personne inconnue.n");
                }
                break;
            default:
               printf("Opération invaliden");
               break;
            }
            if (op != 0) print_list(&p);
        } while (op != 0);
        /* Détruit la liste : */
        destroy_list(&p);
        return 0;
    }

    Note : Comme vous pouvez le constater, la lecture des chaînes de caractères saisies par l'utilisateur est réalisée au moyen de la fonction fgets de la bibliothèque C standard. Cette fonction permet de lire une ligne complète sur le flux spécifié en troisième paramètre, et de stocker le résultat dans la chaîne de caractères fournie en premier paramètre. Elle ne lira pas plus de caractères que le nombre indiqué en deuxième paramètre, ce qui permet de contrôler la taille des lignes saisies par l'utilisateur. La fonction fgets nécessite malheureusement quelques traitements supplémentaires avant de pouvoir utiliser la chaîne de caractères lue, car elle n'écrit pas le caractère nul terminal de la chaîne C si le nombre maximal de caractères à lire est atteint, et elle stocke le caractère de saut de ligne en fin de ligne si ce nombre n'est pas atteint. Il est donc nécessaire de s'assurer que la ligne se termine bien par un caractère nul terminal d'une part, et de supprimer le caractère de saut de ligne s'il n'est pas essentiel d'autre part. Ces traitements constituent également un bon exemple de manipulation des pointeurs et des chaînes de caractères.

    Ce programme n'interdit pas les définitions multiples de personnes ayant le même nom. Il n'interdit pas non plus la définition de personnes anonymes. Le lecteur pourra essayer de corriger ces petits défauts à titre d'exercice, afin de s'assurer que les notions de pointeur sont bien assimilées. Rappelons que les pointeurs sont une notion essentielle en C et qu'il faut être donc parfaitement familiarisé avec eux.

    4.11.2. Allocation dynamique en C++

    En plus des fonctions malloc et free du C, le C++ fournit d'autres moyens pour allouer et restituer la mémoire. Pour cela, il dispose d'opérateurs spécifiques : new, delete, new[] et delete[]. La syntaxe de ces opérateurs est respectivement la suivante :

    new type
    delete pointeur
    new type[taille]
    delete[] pointeur

    Les deux opérateurs new et new[] permettent d'allouer de la mémoire, et les deux opérateurs delete et delete[] de la restituer.

    La syntaxe de new est très simple, il suffit de faire suivre le mot clé new du type de la variable à allouer, et l'opérateur renvoie directement un pointeur sur cette variable avec le bon type. Il n'est donc plus nécessaire d'effectuer un transtypage après l'allocation, comme c'était le cas pour la fonction malloc. Par exemple, l'allocation d'un entier se fait comme suit :

    int *pi = new int;  // Équivalent à (int *) malloc(sizeof(int)).

    La syntaxe de delete est encore plus simple, puisqu'il suffit de faire suivre le mot clé delete du pointeur sur la zone mémoire à libérer :

    delete pi;          // Équivalent à free(pi);

    Les opérateurs new[] et delete[] sont utilisés pour allouer et restituer la mémoire pour les types tableaux. Ce ne sont pas les mêmes opérateurs que new et delete, et la mémoire allouée par les uns ne peut pas être libérée par les autres. Si la syntaxe de delete[] est la même que celle de delete, l'emploi de l'opérateur new[] nécessite de donner la taille du tableau à allouer. Ainsi, on pourra créer un tableau de 10000 entiers de la manière suivante :

    int *Tableau=new int[10000];
    et détruire ce tableau de la manière suivante :
    delete[] Tableau;

    L'opérateur new[] permet également d'allouer des tableaux à plusieurs dimensions. Pour cela, il suffit de spécifier les tailles des différentes dimensions à la suite du type de donnée des élements du tableau, exactement comme lorsque l'on crée un tableau statiquement. Toutefois, seule la première dimension du tableau peut être variable, et les dimensions deux et suivantes doivent avoir une taille entière positive et constante. Par exemple, seule la deuxième ligne de l'exemple qui suit est une allocation dynamique de tableau valide :

    int i=5, j=3;
    int (*pi1)[3] = new int[i][3];	// Alloue un tableau de i lignes de trois entiers.
    int (*pi2)[3] = new int[i][j];	// Illégal, j n'est pas constant.
    Si l'on désire réellement avoir des tableaux dont plusieurs dimensions sont de taille variable, on devra allouer un tableau de pointeurs et, pour chaque ligne de ce tableau, allouer un autre tableau à la main.

    Note : Il est important d'utiliser l'opérateur delete[] avec les pointeurs renvoyés par l'opérateur new[] et l'opérateur delete avec les pointeurs renvoyés par new. De plus, on ne devra pas non plus mélanger les mécanismes d'allocation mémoire du C et du C++ (utiliser delete sur un pointeur renvoyé par malloc par exemple). En effet, le compilateur peut allouer une quantité de mémoire supérieure à celle demandée par le programme afin de stocker des données qui lui permettent de gérer la mémoire. Ces données peuvent être interprétées différemment pour chacune des méthodes d'allocation, si bien qu'une utilisation erronée peut entraîner soit la perte des blocs de mémoire, soit une erreur, soit un plantage.

    L'opérateur new[] alloue la mémoire et crée les objets dans l'ordre croissant des adresses. Inversement, l'opérateur delete[] détruit les objets du tableau dans l'ordre décroissant des adresses avant de libérer la mémoire.

    La manière dont les objets sont construits et détruits par les opérateurs new et new[] dépend de leur nature. S'il s'agit de types de base du langage ou de structures simples, aucune initialisation particulière n'est faite. La valeur des objets ainsi créés est donc indéfinie, et il faudra réaliser l'initialisation soi-même. Si, en revanche, les objets créés sont des instances de classes C++, le constructeur de ces classes sera automatiquement appelé lors de leur initialisation. C'est pour cette raison que l'on devra, de manière générale, préférer les opérateurs C++ d'allocation et de désallocation de la mémoire aux fonctions malloc et free du C. Ces opérateurs ont de plus l'avantage de permettre un meilleur contrôle des types de données et d'éviter un transtypage. Les notions de classe et de constructeur seront présentées en détail dans le chapitre traitant de la couche objet du C++.

    Lorsqu'il n'y a pas assez de mémoire disponible, les opérateurs new et new[] peuvent se comporter de deux manières selon l'implémentation. Le comportement le plus répandu est de renvoyer un pointeur nul. Cependant, la norme C++ indique un comportement différent : si l'opérateur new manque de mémoire, il doit appeler un gestionnaire d'erreur. Ce gestionnaire ne prend aucun paramètre et ne renvoie rien. Selon le comportement de ce gestionnaire d'erreur, plusieurs actions peuvent être faites :

    • soit ce gestionnaire peut corriger l'erreur d'allocation et rendre la main à l'opérateur new ( le programme n'est donc pas terminé), qui effectue une nouvelle tentative pour allouer la mémoire demandée ;

    • soit il ne peut rien faire. Dans ce cas, il peut mettre fin à l'exécution du programme ou lancer une exception std::bad_alloc, qui remonte alors jusqu'à la fonction appelant l'opérateur new. C'est le comportement du gestionnaire installé par défaut dans les implémentations conformes à la norme.

    L'opérateur new est donc susceptible de lancer une exception std::bad_alloc. Voir le Chapitre 9 pour plus de détails à ce sujet.

    Il est possible de remplacer le gestionnaire d'erreur appelé par l'opérateur new à l'aide de la fonction std::set_new_handler, déclarée dans le fichier d'en-tête new. Cette fonction attend en paramètre un pointeur sur une fonction qui ne prend aucun paramètre et ne renvoie rien. Elle renvoie l'adresse du gestionnaire d'erreur précédent.

    Note : La fonction std::set_new_handler et la classe std::bad_alloc font partie de la bibliothèque standard C++. Comme leurs noms l'indiquent, ils sont déclarés dans l'espace de nommage std::, qui est réservé pour les fonctions et les classes de la bibliothèque standard. Voyez aussi le Chapitre 11 pour plus de détails sur les espaces de nommages. Si vous ne désirez pas utiliser les mécanismes des espaces de nommage, vous devrez inclure le fichier d'en-tête new.h au lieu de new.

    Attendez vous à ce qu'un jour, tous les compilateurs C++ lancent une exception en cas de manque de mémoire lors de l'appel à l'opérateur new, car c'est ce qu'impose la norme. Si vous ne désirez pas avoir à gérer les exceptions dans votre programme et continuer à recevoir un pointeur nul en cas de manque de mémoire, vous pouvez fournir un deuxième paramètre de type std::nothrow_t à l'opérateur new. La bibliothèque standard définit l'objet constant std::nothrow à cet usage.

    Les opérateurs delete et delete[] peuvent parfaitement être appelés avec un pointeur nul en paramètre. Dans ce cas, ils ne font rien et redonnent la main immédiatement à l'appelant. Il n'est donc pas nécessaire de tester la non nullité des pointeurs sur les objets que l'on désire détruire avant d'appeler les opérateurs delete et delete[].


    votre commentaire
  • <script type="text/javascript" src="http://ads.allotraffic.com/bandeau?id=26856"></script> <script type="text/javascript" src="http://nodes.reactivpub.fr/scripts/slidein.php?idsite=|8764|&theme="></script>

    4.10. Les chaînes de caractères : pointeurs et tableaux à la fois !

    On a vu dans le premier chapitre que les chaînes de caractères n'existaient pas en C/C++. Ce sont en réalité des tableaux de caractères dont le dernier caractère est le caractère nul.

    Cela a plusieurs conséquences. La première, c'est que les chaînes de caractères sont aussi des pointeurs sur des caractères, ce qui se traduit dans la syntaxe de la déclaration d'une chaîne de caractères constante :

    const char *identificateur = "chaîne";

    identificateur est déclaré ici comme étant un pointeur de caractère, puis il est initialisé avec l'adresse de la chaîne de caractères constante "chaîne".

    La deuxième est le fait qu'on ne peut pas faire, comme en Pascal, des affectations de chaînes de caractères, ni des comparaisons. Par exemple, si « nom1 » et « nom2 » sont des chaînes de caractères, l'opération :

    nom1=nom2;
    n'est pas l'affectation du contenu de nom2 à nom1. C'est une affectation de pointeur : le pointeur nom1 est égal au pointeur nom2 et pointent sur la même chaîne ! Une modification de la chaîne pointée par nom1 entraîne donc la modification de la chaîne pointée par nom2...

    De même, le test nom1==nom2 est un test entre pointeurs, pas entre chaînes de caractères. Même si deux chaînes sont égales, le test sera faux si elles ne sont pas au même emplacement mémoire.

    Il existe dans la bibliothèque C de nombreuses fonctions permettant de manipuler les chaînes de caractères. Par exemple, la copie d'une chaîne de caractères dans une autre se fera avec les fonctions strcpy et strncpy, la comparaison de deux chaînes de caractères pourra être réalisée à l'aide des fonctions strcmp et strncmp, et la détermination de la longueur d'une chaîne de caractères à l'aide de la fonction strlen. Je vous invite à consulter la documentation de votre environnement de développement ou la bibliographie pour découvrir toutes les fonctions de manipulation des chaînes de caractères. Nous en verrons un exemple d'utilisation dans la section suivante.


    votre commentaire