[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