Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

cast de pointeurs de fonctions

8 réponses
Avatar
batyann811
Bonjour,

Je suis en train d'écrire un ensemble de fonction C pour gérer des
listes chaînées génériques. L'une de ces fonctions permet d'executer un
traitement sur chaque element d'une liste.

Voici son prototype :


void LIST_Traverse(LIST * list, void ( * func) (void *) );


Je veux éxecuter la fonction suivante sur chaque élément de ma liste :


void STRING_ToUpper(STRING * str);
/* STRING est un type perso pas char * */


Dans mon code j'essaye donc de faire un truc du genre :

LIST_Traverse(ma_list, STRING_ToUpper);

Je compile avec gcc et là :

main.c:69: warning: passing arg 2 of `LIST_Traverse' from incompatible
pointer type

Normal les 2 fonctions n'ont pas exactement le même prototypes. Ma
question est donc quel syntaxe utiliser pour faire le cast ?

J'ai réussi à me débrouiller en passant par la déclaration d'un type
pointeur de fonction (en plus ça me semble plus propre).


typedef void (* func_traverse)(void *);
void LIST_Traverse(LIST * list, fn_traverse func );

LIST_Traverse(list, (fn_traverse)STRING_ToUpper);


Cela marche parfaitement mais juste par curiosité je voudrais savoir
comment faire sans type intermédiaire par un 'simple' cast.

J'ai aussi remarqué que les 2 appels suivants passent sans problème la
compilation et fonctionnent très bien :


LIST_Traverse(ma_list, STRING_ToUpper);
LIST_Traverse(ma_list, &STRING_ToUpper);


Y a t'il une syntaxe plus 'ANSI' que l'autre ?

Merci.

8 réponses

Avatar
Xavier Roche
batyann811 a écrit :
Dans mon code j'essaye donc de faire un truc du genre :
LIST_Traverse(ma_list, STRING_ToUpper);



LIST_Traverse(ma_list, (void (*)(void*)) STRING_ToUpper);
Mais c'est terriblement crade.

La solution un peu plus propre est d'utiliser le bon prototype:

void STRING_ToUpper(void *arg) {
STRING * const str = (STRING*) arg;
...
}

(Accessoirement "typedef void (*MaFonction_t)(void*);" serait plus
lisible pour des cast.)
Avatar
batyann811
Xavier Roche wrote:


LIST_Traverse(ma_list, (void (*)(void*)) STRING_ToUpper);
Mais c'est terriblement crade.




Effectivement c'est crade (et je me doutais bien que ça le serait) mais
c'était surtout pour satisfaire ma curiosité.

La solution un peu plus propre est d'utiliser le bon prototype:

void STRING_ToUpper(void *arg) {
STRING * const str = (STRING*) arg;
...
}



Oui mais non car cela me prive du contrôle de type sur STRING_ToUpper.


(Accessoirement "typedef void (*MaFonction_t)(void*);" serait plus
lisible pour des cast.)



C'est ce que j'ai déjà fait et c'est ce qui me semble le plus propre.


Merci.
Avatar
-ed-
On 9 déc, 16:06, batyann811 wrote:
Je suis en train d'écrire un ensemble de fonction C pour gérer des
listes chaînées génériques. L'une de ces fonctions permet d'execu ter un
traitement sur chaque element d'une liste.

Voici son prototype :

    void LIST_Traverse(LIST * list, void ( * func) (void *) );

Je veux éxecuter la fonction suivante sur chaque élément de ma list e :

    void STRING_ToUpper(STRING * str);
    /* STRING est un type perso pas char * */

Dans mon code j'essaye donc de faire un truc du genre :

    LIST_Traverse(ma_list, STRING_ToUpper);

Je compile avec gcc et là :

main.c:69: warning: passing arg 2 of `LIST_Traverse' from incompatible
pointer type

Normal les 2 fonctions n'ont pas exactement le même prototypes.




Lorsqu'on utilise un pointeur de fonction, la fonction doit avoir
strictement le même prototype que celui du pointeur de fonction.

Or

void ( * func) (void *)

et

void STRING_ToUpper(STRING * str);

n'ont pas le même prototype. Il faut donc passer par un callback du
même type :

void cb_STRING_ToUpper (void* user)
{
STRING_ToUpper (user);
}

et maintenant, l'appel de la fonction se fait sans histoires :

LIST_Traverse(list, cb_STRING_ToUpper);



Ma
question est donc quel syntaxe utiliser pour faire le cast ?



Ne pas faire de cast. La solution a été donnée au-dessus.

<...> déclaration d'un type
pointeur de fonction (en plus ça me semble plus propre).



Oui, je recommande cette pratique, ça facilite la lecture et la
maintenance.

   typedef void (* func_traverse)(void *);
   void LIST_Traverse(LIST * list, fn_traverse func );



Par contre je déconseille d'inclure le *, car ça empêche d'utiliser l e
type pour définir un prototype de fonction.

typedef void (LIST_traverse_f)(void *);
void LIST_Traverse(LIST * list, LIST_traverse_f *fp );

...

LIST_traverse_f cb_LIST_traverse;


   LIST_Traverse(list, (fn_traverse)STRING_ToUpper);



Un cast masque un problème mais ne le corrige pas.

Cela marche parfaitement mais juste par curiosité je voudrais savoir
comment faire sans type intermédiaire par un 'simple' cast.



Voir au-dessus.

J'ai aussi remarqué que les 2 appels suivants passent sans problème l a
compilation et fonctionnent très bien :

   LIST_Traverse(ma_list, STRING_ToUpper);
LIST_Traverse(ma_list, &STRING_ToUpper);



ceci aussi fonctionne :

LIST_Traverse(ma_list, &&&&&&&&&&&&&&&&&&&&&&&STRING_ToUpper);

je te laisse deviner quelle est la bonne syntaxe...

de même

return 0;
return (0);
return ((((((((0))))))));
Avatar
-ed-
On 9 déc, 19:52, Xavier Roche wrote:
La solution un peu plus propre est d'utiliser le bon prototype:



Oui.


void STRING_ToUpper(void *arg) {
   STRING * const str = (STRING*) arg;
   ...

}



Le cast ne sert à rien
Avatar
-ed-
On 10 déc, 08:37, batyann811 wrote:
Xavier Roche wrote:

> LIST_Traverse(ma_list, (void (*)(void*)) STRING_ToUpper);
> Mais c'est terriblement crade.

Effectivement c'est crade (et je me doutais bien que ça le serait) mais
c'était surtout pour satisfaire ma curiosité.

> La solution un peu plus propre est d'utiliser le bon prototype:

> void STRING_ToUpper(void *arg) {
>   STRING * const str = (STRING*) arg;
>   ...
> }

Oui mais non car cela me prive du contrôle de type sur STRING_ToUpper.



C'est de la programmation générique, tu n'as pas le choix. Tu passes
par un void*, donc tu perds le contrôle de type. C'est inévitable. La
programmation générique c'est pour les gens réveillés. Tu peux
éventuellement ajouter une surcouche applicative pour sécuriser
l'usage à destination de programmeurs lambda.
Avatar
batyann811
-ed- wrote:

C'est de la programmation générique, tu n'as pas le choix. Tu passes
par un void*, donc tu perds le contrôle de type. C'est inévitable. La
programmation générique c'est pour les gens réveillés. Tu peux
éventuellement ajouter une surcouche applicative pour sécuriser
l'usage à destination de programmeurs lambda.




Euh... Ben si j'ai le choix...

Je voulais juste dire que je ne veux pas changer le prototype de ma
fonction STRING_ToUpper juste pour pouvoir l'utiliser avec LIST_Traverse.

La bonne solution est donc (pour moi) de passer par un cast de la
fonction que je passe en paramètre pas comme me le proposait Xavier de
redefinir de prototype la fonction que je passe en paramètre. Sinon je
perd le contrôle de type pour toutes utilisations de STRING_ToUpper.

Et puis je ne vais pas redefinir le prototype de toutes mes fonctions
succeptibles de servir un jour de paramètre à LIST_Traverse...
Avatar
batyann811
-ed- wrote:


Oui, je recommande cette pratique, ça facilite la lecture et la
maintenance.




Effectivement c'est ce qui me parait le plus propre.


Par contre je déconseille d'inclure le *, car ça empêche d'utiliser le
type pour définir un prototype de fonction.

typedef void (LIST_traverse_f)(void *);
void LIST_Traverse(LIST * list, LIST_traverse_f *fp );

...

LIST_traverse_f cb_LIST_traverse;



Effectivement ça me parait mieux je vais donc de ce pas aller virer les *


Un cast masque un problème mais ne le corrige pas.




Oui mais ça peut éviter d'avoir à créer 623 fonctions callback...

Merci à toi et à Xavier pour ces conseils.
Avatar
Antoine Leca
>>> void LIST_Traverse(LIST * list, void ( * func) (void *) );
Je veux éxecuter la fonction suivante sur chaque élément de ma liste
void STRING_ToUpper(STRING * str);
/* STRING est un type perso pas char * */

Dans mon code j'essaye donc de faire un truc du genre :
LIST_Traverse(ma_list, STRING_ToUpper);
Normal les 2 fonctions n'ont pas exactement le même prototypes.


void ( * func) (void *)
et
void STRING_ToUpper(STRING * str);
n'ont pas le même prototype.





Plus précisement, le problème ici est le format de l'argument str; en haut,
c'est un pointeur vers void, donc supposé capable de pointer vers n'importe
quel adresse; tandis qu'en bas, str n'est plus qu'un pointeur vers STRING,
ce peut être un format de pointeur différent, par exemple avec moins de bits
pour le représenter (l'idée étant que si le compilateur « sait » que STRING
est toujours aligné sur une frontière de X octets, il n'est pas nécessaire
de garder les bits de poids faible).

Évidemment, LIST_Traverse N'est PAS au courant des particularités de la
représentation des STRING*...


Il faut donc passer par un callback du même type :

void cb_STRING_ToUpper (void* user) {
STRING_ToUpper (user); }






En news:4940e330$0$929$, batyann811 va escriure:
-ed- wrote:
Ma question est donc quel syntaxe utiliser pour faire le cast ?


Un cast masque un problème mais ne le corrige pas.



Oui mais ça peut éviter d'avoir à créer 623 fonctions callback...



À vouloir économiser 623 (?) fonctions callback, tu vas te priver d'un peu
de portabilité, et introduire par le cast une possibilité réelle de
supprimer des messages intéressants d'avertissement de la part du
compilateur.


Par ailleurs, si l'argument c'est d'économiser des touches, je ne suis pas
sûr que le cast soit gagnant : les call-backs se résument à modifier les
appels à LIST_Traverse (et encore, ce peut être fait grâce à une macro de
présentation), tandis que la déclaration des call-backs se résume à
l'écriture d'une macro cpp

#define DECL_CALLBACK(fonction, params, args)
void cb_##fonction params { fonction args; }

plus une ribambelle d'utilisation de cette macro (attention à ne pas mettre
de point-virgule à la fin) :

DECL_CALLBACK( STRING_ToUpper,
/* prototype . . */ (void* user),
/* armure d'appel*/ (user) )


Bienvenue dans le monde étrange de cpp, celui qu'on n'en voit pas en C++.


Antoine