Des pointeurs et des hommes (5)
Vous commencez j’en suis sûr, à mieux comprendre les tableaux. Aujourd’hui, je vous propose de découvrir les tableaux dynamiques, et au travers d’eux des fonctions qui vont vous accompagner durant toute votre apprentissage du C : malloc et free.
Vous êtes prêts ? Prenez un café, installez vous confortablement, c’est reparti pour un nouveau chapitre !
Tableaux statiques et tableaux dynamiques
Jusqu’ici, nous avons utilisé la syntaxe suivante pour déclarer un tableau :
Par exemple :
Ce tableau est ce que l’on appelle un tableau statique, car sa taille est censée être connue au moment de la compilation. Dans les premières versions du C, il était ainsi impossible d’utiliser une variable pour donner la taille d’un tableau.
Ceci va changer avec la norme C99, qui va introduire cette possibilité avec les “variable length arrays”, ou VLA. Cette norme vise à rendre plus souple l’utilisation des tableaux en autorisant des codes ayant cette forme :
Comme vous pouvez le voir, on utilise ici une variable pour spécifier la taille du tableau !
Néanmoins, si cela est parfois pratique, il existe quelques contraintes lors de l’utilisation d’un VLA. Par exemple, il n’est pas possible de déclarer un tel tableau en tant que variable globale, ni avec le mot clé “static” car le standard est clair à ce sujet :
Ceci n’est pas très surprenant : cela vient tout simplement du fait que les tailles des sections data et bss, où sont stockées les variables globales, doivent être connues dès le chargement de l’exécutable.
De plus, utiliser un VLA provoque une complexification non négligeable de l’assembleur généré par notre compilateur :
Mais alors ? Faut-il les éviter ? Sommes nous condamnés à utiliser des tableaux de taille fixe ?
Certains (et mes profs en faisaient partie) vous diront que oui. Mais si vous avez l’habitude de me lire, vous savez sûrement que je n’aime pas les avis trop tranchés sur les choses.
Les VLA ont une utilité, mais il faut simplement avoir conscience de cette complexité et se demander avant de les utiliser si elle est un problème ou pas dans votre code. Et la question se résume bien souvent à “En ai-je vraiment besoin ici ?”. A vous de répondre à cette question au cas par cas … :)
Une autre façon pour le programmeur de faire des tableaux de taille variable, c’est d’utiliser la fonction “malloc”.
Les tableaux dynamiques : malloc et free
L’utilisation de malloc nécessite l’inclusion dans votre code de stdlib.h : elle fait partie de la librairie standard du C. Vous la retrouverez donc sur tous les OS, même les plus inavouables. Malloc permet justement de faire une requête à cet OS, et lui demander de nous réserver une certaine quantité de mémoire.
Ceci tombe à point car nous avons vu, dans le chapitre précédant de cette série, qu’un tableau n’était justement que cela : un espace mémoire dans lequel les valeurs sont stockées les unes derrière les autres. Voici un exemple simple de création et d’utilisation d’un tel tableau :
La fonction malloc permet de réserver durant l’exécution du programme de la mémoire pour stocker nos valeurs. C’est ce qui explique d’ailleurs l’expression “tableaux dynamiques” qui est généralement utilisée pour désigner ce type de tableaux.
Première chose à observer ici : ce que nous demandons, nous devons le rendre. C’est pourquoi tout espace mémoire réservé à l’aide de “malloc” doit être libéré lorsque nous n’en avons plus besoin à l’aide de la fonction “free”.
Regardons maintenant en détail l’utilisation de malloc :
Malloc ne prend qu’un seul argument : la quantité de mémoire que nous désirons obtenir. Puisque nous souhaitons créer un tableau de 10 entiers, nous avons utilisé l’expression “10*sizeof(int)” soit, si vous préférez, “10 fois la taille en octets d’un entier”.
Comment ? Pourquoi ? Oui, un entier faisant 32 bits sur notre PC, nous aurions pu demander 40 Octets directement. Cela aurait parfaitement fonctionné et le compilo n’aurait même pas râlé. Mais nous sommes entre gens de bonne société ici, et nous respectons la portabilité ! Rien, dans la norme du C, ne vous dit que l’entier fait 4 octets … Et qui sait ? Je suis peut-être en train de recompiler votre code pour le faire tourner sur mon gaufrier hein ? :)
Regardons maintenant la valeur renvoyée par malloc. Voici ce que dit le man :
Comme vous pouvez le constater, malloc renvoie un pointeur non typé, ou void*. Cela est normal : nous avons demandé une certaine quantité de mémoire, l’OS nous l’a affecté. Nous ne lui avons pas dit ce que nous comptions en faire. Cette adresse, nous souhaitons la stocker dans une variable, tab, de type int* pour pouvoir accéder aux entiers les uns après les autres.
Pour faire les choses bien, nous pourrions faire un cast : nous disons au compilateur que oui, le résultat de malloc, nous souhaitons bien le transformer en int* avant de le mettre dans notre variable. En pratique, ce n’est pas nécessaire car le void* sera automatiquement promu dans le bon type et cela pourrait masquer des erreurs ( par exemple si vous oubliez d’inclure stdlib.h ). On ne fait donc généralement pas de cast pour malloc.
Comme toutes les requêtes, notre demande de mémoire peut être refusée. C’est rare, mais parfois, malloc peut juste échouer. Là encore, la page de man nous indique la voie :
On ajoute donc habituellement un petit test pour vérifier la valeur de retour de malloc, et agir le cas échéant :
Leave free ( or die hard … )
Pour libérer la mémoire réservée par votre programme à l’aide de malloc, on utilise la fonction “free”. Son usage est relativement simple : il suffit de lui passer l’adresse que vous avez obtenu de malloc, et celle-ci s’occupera de la libération de la mémoire.
Il est à noter que la fonction free ne renvoie pas de valeur ni d’erreur : Le standard du C ( version C99 ici ) est très claire à ce sujet :
Là où il faut être prudent, c’est que la valeur contenue dans la variable tab n’a pas été altérée par la fonction free. Notre tab contient toujours l’adresse, mais nous ne sommes plus censés l’utiliser puisque nous avons libéré cet espace mémoire. Pour éviter cette erreur très classique, il est recommandé de mettre ce pointeur à la valeur NULL afin d’éviter tout usage postérieur.
Et si je ne libère pas la mémoire ? C’est grave ?
Et bien … Pas tant que ça en pratique ! Car bien heureusement, l’OS veille en général sur vous ! Il s’occupera, à la mort de votre process, de s’assurer que la mémoire qui vous a été affectée est bien libérée. C’est en tout cas le cas pour tous les OS modernes que je connais !
Ceci étant dit, ce n’est pas parce qu’oublier un free ne déclenchera pas un massacre sanglant de chatons qu’il ne faut pas y prêter attention dans vos codes. Tout d’abord, cela reste en général un indicateur d’un code de bonne qualité. Ensuite, et c’est probablement plus important, vous risquez d’utiliser un malloc au sein d’une boucle, ce qui réservera de plus en plus de mémoire, jusqu’à souvent dépasser les ressources disponibles sur votre machine.
Bref, ne pas libérer la mémoire, c’est pas bien !
Conclusion
Chaleur oblige -Ce sont les joies du blogging au mois d’août- je vais en rester là pour aujourd’hui. Nous verrons dans le prochain article en quoi les tableaux dynamiques sont différents des tableaux statiques.
A bientôt !
Rancune.