OVH Cloud OVH Cloud

cast de pointeur *, ** différence ?

114 réponses
Avatar
Nico
salut,

j'ai un petit code qui m'étonne... soit une fonction prenant un void*
en paramètre, lors de son appel, si je lui passe un double*, mon
compilateur dit rien... ok.
soit une fonction retournant un void *, lorsque j'assigne un double*
avec le retour de cette fonction, mon compilateur ne dit rien...

ici donc, pas besoin de caster ni le paramètre (en (void*)), ni le
retour de fonction (en (double*))...

maintenant, on prend les même et on recommence, à la différence près
que mon argument n'est plus un void* mais un void**, et mon retour
n'est plus un void* mais aussi un void **. là plus rien ne passe...
lors de l'appel de la fonction je suis obligé de caster mon double** en
void**, et lors du retour, je suis obligé de caster en double**,
pourquoi ?

pour un exemple plus parlant, considérez le code suivant :

void *
vect_new(size_t len, size_t size_type)
{
void *v = malloc(len * size_type);
return v;
}


void
vect_free(void *vect)
{
if(vect) free(vect);
}

/*declaration du vecteur...
pas besoin de faire un cast (double*)*/

double *vect = vect_new(3,sizeof(double));

/*appel sans probleme a la compilation :
pas besoin de faire (void*)vect*/
vect_free(vect);


et maintenant avec des void** :

void **
mat_new(unsigned int nrow, unsigned int ncol, size_t size_type)
{
int i,j;
void **m = malloc(nrow * size_type);
if(m)
{
for(i=0; i<nrow; i++)
{
m[i] = malloc(ncol * size_type);
if(!m[i])
{
for(j=0; j<=i; j++)
free(m[i]);
free(m);
m = NULL;
}
}
}
return m;
}

void
mat_free(void **m,unsigned int nrow)
{
int i;
if(m)
{
for(i=0; i<nrow; i++)
if(m[i])
free(m[i]);
free(m);
}
}

/*declaration de la matrice*/
double **matrice = (double**)mat_new(4,3,sizeof(double));

/*appel sans probleme a la compilation : */
mat_free((void**)matrice,4);

là si je cast pas l'un et l'autre... warning... où se situe la
différence avec le 1er exemple ?

merci de m'éclairer à ce propos.

--
Nico,
http://astrosurf.com/nicoastro
http://nicolas.aunai.free.fr

10 réponses

1 2 3 4 5
Avatar
Charlie Gordon
"Nico" wrote in message
news:
salut,

j'ai un petit code qui m'étonne... soit une fonction prenant un void*
en paramètre, lors de son appel, si je lui passe un double*, mon
compilateur dit rien... ok.
soit une fonction retournant un void *, lorsque j'assigne un double*
avec le retour de cette fonction, mon compilateur ne dit rien...

ici donc, pas besoin de caster ni le paramètre (en (void*)), ni le
retour de fonction (en (double*))...

maintenant, on prend les même et on recommence, à la différence près
que mon argument n'est plus un void* mais un void**, et mon retour
n'est plus un void* mais aussi un void **. là plus rien ne passe...
lors de l'appel de la fonction je suis obligé de caster mon double** en
void**, et lors du retour, je suis obligé de caster en double**,
pourquoi ?


C'est bien dommage en effet.
Le fait que l'on puisse caster implicitement X** et void* est une cause de bug.

void **m = malloc(nrow * size_type);


Non, c'est faux, tu devrais toujours utiliser la version plus sûre :

void **m = malloc(nrow * sizeof(*m));

Ou encore plus sûr :

void **m = calloc(nrow, sizeof(*m));

Mais tu fais en outre la supposition que void* fait la même taille que double*.
C'est pas très risqué, mais sois conscient que cela peut être faux pour certains
environnements.

Réciproquement, j'ai souvent pesté contre la sémantique des void* qui m'empêche
d'écrire sans cast le code suivant :

void freep(void **pp) {
free(*p);
*p = NULL;
}

char *p = malloc(1000);
...
freep(&p);

En conclusion, C est trop primitif, on voudrait y rajouter juste un zeste de C++
: new, delete, des types génériques... tableaux multidimensionnels
dynamiques... mais c'est un engrenage infernal ;-)

Chqrlie.

Avatar
Richard Delorme

void **m = malloc(nrow * size_type);


Non, c'est faux, tu devrais toujours utiliser la version plus sûre :

void **m = malloc(nrow * sizeof(*m));

Ou encore plus sûr :

void **m = calloc(nrow, sizeof(*m));


Pourquoi calloc() est plus sûr que malloc() ?
calloc() ne crée pas un tableau de pointeurs nuls, sauf sur les
implémentations où le pointeur nul est représenté par des bits tous nuls.

--
Richard


Avatar
Jean-Marc Bourguet
Gabriel Dos Reis writes:

Nico writes:

| Jean-Marc Bourguet avait soumis l'idée :
|
|
| > void* est un cas particulier.
|
| pourquoi ?

Because someone got clever.


Je l'avais prevu :-)

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:418f2064$0$15749$

void **m = malloc(nrow * size_type);


Non, c'est faux, tu devrais toujours utiliser la version plus sûre :

void **m = malloc(nrow * sizeof(*m));

Ou encore plus sûr :

void **m = calloc(nrow, sizeof(*m));


Pourquoi calloc() est plus sûr que malloc() ?
calloc() ne crée pas un tableau de pointeurs nuls, sauf sur les
implémentations où le pointeur nul est représenté par des bits tous nuls.


Je n'ai pas dit cela.

Mais peux-tu citer une architecture où ce n'est pas vrai ?

calloc est plus sûr que malloc parce que la mémoire est initialisée à 0, ce qui
rend le comportement des programmes plus reproductible dans le cas ou le
programmeur "oublie" d'initialiser tout ou partie de la zone allouée. Cela
permet même de ne pas avoir à initialiser les champs qui doivent êtres nuls.
Cela permet aussi que les tableaux de char, qui sont souvent valués par un
strcpy ne contiennent pas de résidus qui pourraient malencontreusement se
propager sur disque, voire ailleurs, lors de stockages bruts de structures, une
pratique peu recommandable, non portable et peu flexible, mais malheureusement
assez courante.

Une bonne pratique défensive consiste à NULLifier les pointeurs après l'appel de
free(). Ce n'est ni necessaire, ni suffisant pour éviter les bugs de mémoire,
mais cela permet d'en attraper certains. De plus dans le code non critique, il
n'est pas nécessaire de tester les pointeurs avant de les passer à free() qui
accepte NULL sans sourciller. Au besoin, utiliser un wrapper dans les
environnements où free() est beugué.

Realloc() resiste à ce genre de précaution : on ne sait pas toujours quelle
partie initialiser à 0... Il faut dire que cette fonction est particulièrement
mal famée, l'éviter est une sage précaution. Personnellement, dans les projets
où c'est vraiment nécéssaire, j'utilise le wrapper suivant :

void *reallocp(void **pp, size_t size, size_t count, size_t last_count) {
void *p = realloc(*pp, size * count);
if (p) {
if (count > last_count)
memset((char *)p + size * count, 0, size * (count - last_count));
*pp = p;
}
return p;
}

Cela évite la variable intermédiaire et le code qui la traite, qui est un nid à
bugs.

Evidemment, le problème de cast implicite de X** en void** me fait vraiment
chier ici aussi.

Chqrlie.



Avatar
Horst Kraemer
Gabriel Dos Reis wrote:

Emmanuel Delahaye writes:

| Jean-Marc Bourguet wrote on 07/11/04 :
|
| > void* est un cas particulier. Il faut un cast pour les
| > autres. Note que ça peut être dangereux dans les cas où
| > double* n'a pas la même taille que void*.
|
| C'est possible ça?

double** n'est pas censé avoir la même tailler que void**.


Oui, mais le problème du code présenté est plutot la taille de void*
vs. double* et non la taille de void** vs. double** ;-)

--
Horst

Avatar
Jean-Marc Bourguet
Emmanuel Delahaye writes:

Jean-Marc Bourguet wrote on 07/11/04 :

void* est un cas particulier. Il faut un cast pour les
autres. Note que ça peut être dangereux dans les cas où
double* n'a pas la même taille que void*.


C'est possible ça?


Quoi que void* n'ait pas la meme taille que double*? Oui.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Marc Boyer
In article , Jean-Marc Bourguet wrote:
Nico writes:

salut,

j'ai un petit code qui m'étonne... soit une fonction prenant un void* en
paramètre, lors de son appel, si je lui passe un double*, mon compilateur
dit rien... ok.
soit une fonction retournant un void *, lorsque j'assigne un double* avec
le retour de cette fonction, mon compilateur ne dit rien...

ici donc, pas besoin de caster ni le paramètre (en (void*)), ni le retour
de fonction (en (double*))...

maintenant, on prend les même et on recommence, à la
différence près que mon argument n'est plus un void* mais
un void**, et mon retour n'est plus un void* mais aussi un
void **. là plus rien ne passe... lors de l'appel de la
fonction je suis obligé de caster mon double** en void**,
et lors du retour, je suis obligé de caster en double**,
pourquoi ?


void* est un cas particulier. Il faut un cast pour les
autres. Note que ça peut être dangereux dans les cas où
double* n'a pas la même taille que void*.


Et ? Le compilateur n'a pas à charge de faire la conversion
binaire qui va bien ?
Tu parles de quel cas là, void* ou void** ?

Pour être plus clair: soit la fonction
void foo(void*);
est-ce que je peux l'appeller avec
double *p= &qui_va_bien;
foo(p);
ou est-ce qu'il faut explicitement faire
foo( (void*) p );


Merci,
Marc Boyer
--
Je ne respecte plus le code de la route à vélo depuis une double fracture
due au fait que j'étais le seul à le respecter.


Avatar
Jean-Marc Bourguet
Marc Boyer writes:

In article , Jean-Marc Bourguet wrote:
Nico writes:
[cast de void** en double**]
Note que ça peut être dangereux dans les cas où double* n'a pas la

même taille que void*.


Et ? Le compilateur n'a pas à charge de faire la conversion binaire
qui va bien ?


Le cast de void** en double** ne va pas aller changer le tableau
intermediaire. Reprenons le coeur de ce que je pensais qu'avait fait
Nico (voir plus bas, il y a un probleme en plus dans ce qu'a fait
Nico):

void** tab = malloc(dim1 * sizeof(void*));
for (int i = 0; i < dim1; ++i) {
tab[i] = malloc(dim2 * sizeof(double));
}

double** theTab = (double**) tab;

Ce cast va ajuster la valeur de tab comme il faut, mais pas celle de
*tab: or on y a stocke des pointeurs vers void, pas des pointeurs vers
double et rien ne garanti qu'ils ont le meme format ou meme la meme
taille. (En pratique je ne connais pas d'architecture recente ou ca
va poser un probleme.)

En realite, Nico n'utilise pas sizeof(void*) pour la premiere
allocation mais sizeof(double). Il est vraissemblable qu'il ne fait
qu'allouer trop car je ne connais pas d'architecture (recente ou non)
ou sizeof(double) < sizeof(void*). Mais s'il y en avait une...

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
Marc Boyer
In article , Jean-Marc Bourguet wrote:
Marc Boyer writes:

In article , Jean-Marc Bourguet wrote:
Nico writes:
[cast de void** en double**]
Note que ça peut être dangereux dans les cas où double* n'a pas la

même taille que void*.


Et ? Le compilateur n'a pas à charge de faire la conversion binaire
qui va bien ?


Le cast de void** en double** ne va pas aller changer le tableau
intermediaire. Reprenons le coeur de ce que je pensais qu'avait fait
Nico (voir plus bas, il y a un probleme en plus dans ce qu'a fait
Nico):


OK, c'était void** vs double**

void** tab = malloc(dim1 * sizeof(void*));
for (int i = 0; i < dim1; ++i) {
tab[i] = malloc(dim2 * sizeof(double));
}

double** theTab = (double**) tab;

Ce cast va ajuster la valeur de tab comme il faut, mais pas celle de
*tab: or on y a stocke des pointeurs vers void, pas des pointeurs vers
double et rien ne garanti qu'ils ont le meme format ou meme la meme
taille. (En pratique je ne connais pas d'architecture recente ou ca
va poser un probleme.)


Ouaip.

Merci,
Marc Boyer
--
Je ne respecte plus le code de la route à vélo depuis une double fracture
due au fait que j'étais le seul à le respecter.




Avatar
Richard Delorme
"Richard Delorme" wrote in message
news:418f2064$0$15749$



void **m = malloc(nrow * size_type);


Non, c'est faux, tu devrais toujours utiliser la version plus sûre :

void **m = malloc(nrow * sizeof(*m));

Ou encore plus sûr :

void **m = calloc(nrow, sizeof(*m));


Pourquoi calloc() est plus sûr que malloc() ?
calloc() ne crée pas un tableau de pointeurs nuls, sauf sur les
implémentations où le pointeur nul est représenté par des bits tous nuls.



Je n'ai pas dit cela.

Mais peux-tu citer une architecture où ce n'est pas vrai ?


http://www.eskimo.com/~scs/C-faq/q5.17.html

calloc est plus sûr que malloc parce que la mémoire est initialisée à 0, ce qui
rend le comportement des programmes plus reproductible dans le cas ou le
programmeur "oublie" d'initialiser tout ou partie de la zone allouée. Cela
permet même de ne pas avoir à initialiser les champs qui doivent êtres nuls.


Non, cela n'initialise pas les champs, mais met tous les bits à 0. En
particulier pour les flottants (float, double, ...) et les pointeurs, il
n'y a aucune garantie que cela corresponde à des valeurs nulles.


Cela permet aussi que les tableaux de char, qui sont souvent valués par un
strcpy ne contiennent pas de résidus qui pourraient malencontreusement se
propager sur disque, voire ailleurs, lors de stockages bruts de structures, une
pratique peu recommandable, non portable et peu flexible, mais malheureusement
assez courante.


C'est marrant que tu critiques strncpy alors qu'il a ce comportement...


Une bonne pratique défensive consiste à NULLifier les pointeurs après l'appel de
free(). Ce n'est ni necessaire, ni suffisant pour éviter les bugs de mémoire,
mais cela permet d'en attraper certains. De plus dans le code non critique, il
n'est pas nécessaire de tester les pointeurs avant de les passer à free() qui
accepte NULL sans sourciller. Au besoin, utiliser un wrapper dans les
environnements où free() est beugué.

Realloc() resiste à ce genre de précaution : on ne sait pas toujours quelle
partie initialiser à 0... Il faut dire que cette fonction est particulièrement
mal famée, l'éviter est une sage précaution. Personnellement, dans les projets
où c'est vraiment nécéssaire, j'utilise le wrapper suivant :

void *reallocp(void **pp, size_t size, size_t count, size_t last_count) {
void *p = realloc(*pp, size * count);
if (p) {
if (count > last_count)
memset((char *)p + size * count, 0, size * (count - last_count));


memset() souffre évidemment du même problème que calloc().


--
Richard




1 2 3 4 5