[gull] Subtilité de C n° 2 : type de retour de malloc

Marc Mongenet marc at mongenet.ch
Tue Apr 1 15:06:19 CEST 2008


Le prototype de la fonction C malloc est :
void *malloc(size_t size);

Il existe deux « écoles » concernant l'assignation du pointeur
retourné par malloc. Celle qui prône la conversion explicite
(cast) du pointeur retourné ; et celle qui prône sa conversion
implicite. Soit :

int *p;
p = (int*)malloc(sizeof(int));   /* école explicite */
p = malloc(sizeof *p);   /* école implicite */

Les deux solutions sont valides en C car les conversions
d'un pointeur de donnée de et vers void* sont implicites
(c'est un peu le but de l'existence de void*).

Une faiblesse de la conversion explicite est de pouvoir
masquer l'oubli de #include <stdlib.h>. En effet, en cas
d'oubli, le compilateur construit une déclaration implicite
de malloc avec int pour type de retour (le type par défaut
de C). Et dans notre exemple, la conversion explicite
convertirait silencieusement en int* ce int implicitement
déclaré. Si int et int* ont par chance la même taille, alors
on ne se rendra probablement compte de rien. Mais s'ils
n'ont pas la même taille, alors on obtiendra probablement
un crash à l'exécution. Si l'on s'était reposé sur une
conversion implicite, le compilateur aurait refusé la
conversion implicite de int en pointeur et le bug serait
apparu à la compilation. Notons toutefois que les
compilateurs modernes peuvent lancer un avertissement
en cas de déclaration implicite de fonction, et qu'il est
unanimement déconseillé de reposer sur la déclaration
implicite des fonctions (c'est même impossible en C++).

Une faiblesse de la conversion implicite est de pouvoir
masquer une désynchronisation entre le type de pointeur
et la taille de l'allocation. Si l'on écrit :
p = malloc(sizeof(int));
et que la déclaration de p est changée (par exemple en
long *p;), alors le compilateur n'émettra aucun
avertissement. Pour éviter cette désynchronisation, on
peut écrire :
p = malloc(sizeof *p);
Mais ce genre de déclaration perd en lisibilité dans les
assignations plus complexes :
*node->element.ref = malloc(sizeof **node->element.ref);
et peut aussi assez facilement finir silencieusement erroné
suite à un copier/coller.

Remarques :
En C++, la conversion avec void* doit être explicite,
mais le problème ne se pose guère car on n'utilise en
principe pas malloc en C++.

En vieux C non standard, malloc retournait souvent un
char* (void n'existait pas à cette époque). Les vieux
compilateurs convertissaient implicitement un peu tout en
n'importe quoi. Mais si l'on utilisait un compilateur plus
moderne (ne convertissant pas implicitement un char* en
int*) avec une vieille bibliothèque C où malloc retournait
un char*, alors il fallait ajouter une conversion explicite.

-- 
Marc Mongenet
Creator of the Web 2 Markup Language
http://w2ml.com



More information about the gull mailing list