[gull] Subtilité de C n° 2 : type de retour de malloc
Daniel Cordey
dc at mjt.ch
Tue Apr 1 15:55:37 CEST 2008
On Tuesday 01 April 2008, Marc Mongenet wrote:
> Le prototype de la fonction C malloc est :
> void *malloc(size_t size);
Oui, c'est la definition POSIX/ANSI (...) !
> Il existe deux « écoles » concernant l'assignation du pointeur
> retourné par malloc.
A mon avis... plus. Il existait deux ecoles, mais ANSI n'en tolere qu'une; a
prtir du moment ou l'on desire coller completement a ANSI et que l'on declare
toutes les signatures des fonctions.
> 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 */
Ce sont deux choses totalement differentes. Je sais que SUN a propage
cette "vision" pendant longtemps et cela a induit passablement de fausses
idees chez bon nombre de developeurs ! Entre autre l'interpretation de **argv
& *argv[]
> 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*).
Cela depend du compilateur (options), de l'architecture et il est tres
dangereux de se reposer la-dessus. En effet, suivant l'architecture, cette
affirmation est erronnee. Il existe des architecture qui requierent un
alignement stricte sur N mots (N >= 1). Ce qui fait qu'un pointeur de 'int'
ne peut etre utillise comme pointeur de 'long' ou de 'char'... si j'ecris :
int *pi;
char *pc:
pi = (int *)(0)
pc = (char *)(0)
pi++;
pc++;
Alors pi != pc !!! On ne peut non plus effectuer d'operation sur un void *
pusique l'on en connait pas le type. Toutefois, il est vrais que l'on peut
souvent "confondre" long & int sur des machines 32/64 bits, puisque, par
convention/habitude, il s'agit des memes entites. On a "bricole" avec la
definition 'long long' pour definir des entiers 64 bits.
De plus, il est bien dit que sizeof ne doit pas etre utilise pour calculer le
nombre de byte d'une structure, justement pour eviter ce probleme
d'alignement des pointeurs lors de l'utilisation de malloc/calloc.
Si l'on desire effectuer des operation mathematiques sur les valeurs des
pointeurs, il convient d'etre extremenet rigoureux et de coller au standard
ANSI. De plus, il faut aussi s'assurer des problemes d'alignements lors de
chaques calculs : value + word & mask_word
Un bon exemple de numero d'equilibriste avec les pointeurs et les offsets des
elemenst de structure est donne pas le code de la librairie Xlib et
Xtoolkit... :-)
> Une faiblesse de la conversion explicite est de pouvoir
> masquer l'oubli de #include <stdlib.h>.
En ANSI, on ne devrait jamais oublier cela et on se doit de declarer toutes
les signatures des fonctions utilisees pour etre sure que le compilateur
affichera les warning lorsque cela s'avere errone. Donc, aucun code, une fois
compile, ne devrait avoir de warning a ce sujet (ou meme d'autres !!!).
COmbien de packages configure/make/makeinstall n'ont pas de warnings a la
compilation ?
> 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).
C'est justement ce qui est dangereux et ce que ANSI a cherche a eliminer.
> Si int et int* ont par chance la même taille, alors
> on ne se rendra probablement compte de rien.
Oui, trompeur et trop de developeurs paresseux se reposent la-dessus.
> Mais s'ils
> n'ont pas la même taille, alors on obtiendra probablement
> un crash à l'exécution.
Pas... brobablement... cartainement. C'est un bus-error...
> 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++).
C++ est... ANSI :-)
> 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++.
Or, C++ utilise des malloc a votre insu... (new...). Je continue a pretendre
que, justement, les mallocs sont tres utiles en C++, a condition d'etre
bien "controles". Le meilleur moyen est de s'ecrire une librairie gerant tous
les mallocs rattaches a une instance et a pouvoir garantir la destruction de
tous lors de la destruction de l'objet. Je l'ai fait et cela ma sauve la vie
en C++ et 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.
Oh oui... et certains souffrent d'une permissivite (bugs ?) coupable :-)
> 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.
gcc compile en mode ANSI par defaut.
dc
More information about the gull
mailing list