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

Leopoldo Ghielmetti leopoldo.ghielmetti at a3.epfl.ch
Wed Apr 2 10:59:28 CEST 2008


On Tue, 2008-04-01 at 21:51 +0200, Marc Mongenet wrote:
> Le 01/04/08, Daniel Cordey<dc at mjt.ch> a écrit :
> > 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 t'assure que ce sont deux manières de faire valides et
> respectant tout à fait les normes. On peut encore écrire :
> p = (int*)malloc(sizeof *p);  /* style peu courant */
> p = malloc(sizeof(int));  /* style très courant */

Moi j'utilise ce que tu appelles le style peu courant et très couramment
d'ailleurs. :-)

> >  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[]
> >
> 
> Ah oui, les relations entre les pointeurs et les tables sont une
> inépuisable source de subtilités en C et j'écrirai surement des
> messages à ce propos à l'occasion. Je ne connais en revanche
> pas cette histoire avec SUN.

Je ne connais pas l'histoire de Sun qui cite Daniel, mais j'ai travaillé
sur des Sun pendant un certain temps et il est vrai que le compilateur
de Sun permettait tout et n'importe quoi sans aucun Warning. En plus sur
les machines 32 bits sizeof(int)==sizeof(long)==sizeof(void*) (ou
n'importe quel pointeur) et les tableaux sont tous parfaitement
interchangeables.
Le résultat c'est que j'ai vu de grosses cochonneries sur les programmes
écrits pour les systèmes Sun.

> >
> >  > 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 doit y avoir un malentendu. Par conversion implicite de
> pointeur de donnée de et vers void*, je voulais dire que :
> int i;
> int *pi = &i;
> void *p;
> p = pi;  /* pas besoin de cast */
> pi = p;  /* pas besoin de cast */
> /* Ici, le standard garantit que pi==&i. */
> 
> >  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 :
> 
> Absolument.
> 
> >  int *pi;
> >  char *pc:
> >
> >  pi = (int *)(0)
> >  pc = (char *)(0)
> >
> >  pi++;
> >  pc++;
> >
> >  Alors pi != pc !!!
> 
> Ce n'est pas une expression valide en C (pointeurs
> incompatibles), mais au niveau machine, les pointeurs
> sont effectivement différents du moment que nous
> avons sizeof(char)!=sizeof(int).
> Mais je ne vois pas bien le rapport mes exemples.
> 
> > On ne peut non plus effectuer d'operation sur un void *
> > pusique l'on en connait pas le type.
> 
> Je suppose que tu veux dire qu'on ne peut pas déréférencer
> un void* ni faire de l'arithmétique dessus (sans utiliser des
> extensions non standards, par exemple de GCC) ? Les
> autres opérations restent possibles (assignations, tests,
> adresse de, taille de...) :
> void *p, **pp;
> p = 0;
> pp = &p;
> if (p || pp) {}
> ...
> 
> >  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.
> 
> L'opérateur sizeof retourne le nombre de bytes de son opérande ;
> c'est sa raison d'être. Appliqué à une structure, il retourne la taille
> de la structure, incluant les éventuels padding à l'intérieur et à la
> fin de la structure.

Ce n'est pas ce que j'ai constaté, j'ai même remarqué que:
struct toto *t;
int i = sizeof(*t);
int j = sizeof(struct toto);

on a i >= j

D'ou j'en ai déduit que sizeof(struct toto) retourne la taille de la
structure sans le padding tandis que sizeof(*t) retourne la taille
exacte utilisée par la structure pointée par t, donc la taille avec le
pad. Un logiciel que j'avais écrit il y a quelques années plantait avec
un beau core dump après une corruption de mémoire causée justement pas
cette différence, j'avais fait un malloc(sizeof(toto)) et ensuite je
remplissait la structure. J'ai résolu le problème en changeant en
malloc(sizeof(*t)).

C'est la raison pour laquelle j'utilise la notation peu courante, le
cast m'assure que je ne peux pas remplacer le type du pointeur tandis
que le sizeof sur la variable m'assure de sa bonne taille.

> Le standard garantit que la fonction malloc retourne un pointeur
> correctement aligné, ou un pointeur nul en cas d'erreur.
> 
> Le Committe Draft du standard C99 que j'ai sous les yeux
> donne les exemples suivants :
> 
> A principal use of the sizeof operator is in communications with
> routines such as storage allocators and I/O systems. A storage-
> allocation function might accept a size (in bytes) of an object to
> allocate and return a pointer to void. For example:
> 
> extern void *alloc(size_t);
> double *dp = alloc(sizeof *dp);
> 
> The implementation of the alloc function should ensure that its
> return value is aligned suitably for conversion to a pointer to
> double.
> 
> >  > 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 par hasard la valeur corrompue reste dans l'espace
> d'adressage du processus et conserve un alignement
> valide, alors il n'y a pas de bus error. On lira ou écrira
> plutôt une valeur à une mauvaise adresse. Un bus error
> peut en découler par la suite, ou pas.
> 
> >
> > C++ est... ANSI :-)
> >
> 
> Il a même été directement ISO.
> 
> >
> > gcc compile en mode ANSI par defaut.
> 
> Plus ou moins. Il accepte aussi des extensions GNU par
> défaut. Il faut utiliser les paramètres -ansi si l'on ne veut
> pas des extensions qui peuvent poser problème. Et le
> paramètre -pedantic est censer rejetter tout ce qui n'est
> pas strictement conforme à la norme.

Et en plus moi j'active tous les warnings avec -Wall et tous les -W qui
sont pertinents. Ceci m'assure d'avoir tous les warnings possibles et
donc de détecter tout ce pourrait causer des problèmes.
Le seul défaut de cette méthode c'est qu'on ne peut jamais avoir un code
qui compile sans warnings car il y a des warnings qui sont
complémentaires, c'est à dire que si on résout le premier on a le
deuxième et vice-versa. En plus il y a certains warnings qu'on ne peut
pas éviter à cause de bizarreries dans la librairie standard (p.e.
l'utilisation d'un void* à la place d'un const void* qui fait que dès
qu'on passe le paramètre à la stdlib on à un warning car elle ne le
considère pas comme const comme il devrait l'être, en fait l'utilisation
des const est très délicate).

Mais au moins ça permet d'avoir un bon contrôle sur ce que l'on fait.

ciao, Leo




More information about the gull mailing list