J'écris une fonction où je voudrais accepter 2 type
de paramètres.
string
(char *)structure
où il y aura n nombre d'éléments. Et pour y parvenir, je pense utiliser un simple void *
comme type de paramètre. Mais je ne sais pas comment vérifier si le paramètre est d'un type ou d'un autre, en toute sécurité.
3 Réponses :
La traduction de void*
est
"Cher compilateur, ceci est un pointeur, et il n'y a aucune information supplémentaire pour vous à ce sujet.".
Habituellement, le compilateur sait mieux que vous (le programmeur), à cause des informations qu'il a obtenues plus tôt et dont il se souvient encore et que vous avez peut-être oublié.
Mais dans ce cas particulier, vous savez mieux ou avez besoin de mieux savoir. Dans tous les cas de void*
les informations sont disponibles autrement, mais uniquement au programmeur, qui "se trouve à savoir". Le programmeur doit donc fournir les informations au compilateur - ou mieux au programme en cours d'exécution, car le seul avantage d'un void*
est que les informations peuvent changer pendant l'exécution.
Habituellement, cela se fait en donnant les informations via des paramètres supplémentaires aux fonctions, parfois via le contexte, c'est-à-dire que le programme "arrive à savoir" (par exemple, pour chaque type possible, il existe une fonction distincte, la fonction appelée implique le type).
Donc à la fin void*
ne contient pas les informations de type.
Beaucoup de programmeurs comprennent mal cela comme "Je n'ai pas besoin de connaître les informations de type".
Mais le contraire est vrai, l'utilisation de void*
augmente la responsabilité du programmeur de garder une trace des informations de type et de les fournir de manière appropriée au programme / compilateur.
De plus, le compilateur sait réellement quel est le type de données pointé. Donc, si vous sautez dans une fonction void*
, transtypez dans le mauvais type, puis dé-référencez les données ... alors toutes sortes de comportements non définis sont invoqués.
void*
sont en quelque sorte obsolètes pour la programmation générique, il n'y a pas beaucoup de situations où vous devriez les utiliser de nos jours. Ils sont dangereux car ils conduisent à une sécurité de type inexistante. Et comme vous l'avez noté, vous perdez également les informations de type, ce qui signifie que vous devrez faire glisser une enum
encombrante avec le void*
.
Au lieu de cela, vous devez utiliser C11 _Generic
qui peut vérifier les types au moment de la compilation et ajouter la sécurité de type. Exemple:
#define type_check(x) _Static_assert(_Generic((x), \ char*: 1, const char*: 1, \ s_t*: 1, const s_t*: 1, \ default: 0), #x": incorrect type.") #define func(x) do{ type_check(x); _Generic((x), \ char*: func_str, const char*: func_str, \ s_t*: func_s, const s_t*: func_s)(x); }while(0)
N'oubliez pas de fournir des versions qualifiées ( const
) de tous les types que vous souhaitez prendre en charge.
Si vous voulez de meilleures erreurs de compilation lorsque l'appelant passe le mauvais type, vous pouvez ajouter une assertion statique:
#include <stdio.h> typedef struct { int n; } s_t; // some struct void func_str (const char* str) { printf("Doing string stuff: %s\n", str); } void func_s (const s_t* s) { printf("Doing struct stuff: %d\n", s->n); } #define func(x) _Generic((x), \ char*: func_str, const char*: func_str, \ s_t*: func_s, const s_t*: func_s)(x) \ int main() { char str[] = "I'm a string"; s_t s = { .n = 123 }; func(str); func(&s); }
Si vous essayez quelque chose comme int x; func(x);
vous obtiendrez le message du compilateur "x: incorrect type"
.
Je vous recommande d'encapsuler vos arguments avec quelque chose comme:
#include <stdio.h> struct WrappedArg{ int Type; void* Pointer; }; typedef struct WrappedArg WrappedArg; struct Other_Structure{ int x, y, z; }; typedef struct Other_Structure Other_Structure; void Our_Function(void* data){ WrappedArg* translated_data = (WrappedArg*) data; if(translated_data->Type == 0){ // print srting, if string passed printf("You passed string: %s\n", (char*) translated_data->Pointer); } else { // recreate structure_data from pointer Other_Structure* structure_data = (Other_Structure*)translated_data->Pointer; printf("You passed sructure with x, y, z: %d, %d, %d\n", structure_data->x, structure_data->y, structure_data->z); } } int main(){ Other_Structure structure_data = {1,2,3}; char* string_data = "Hi there!"; WrappedArg arguments = { 0, // type string_data }; Our_Function((void*)&arguments); // OR arguments.Type = 1; arguments.Pointer = &structure_data; Our_Function((void*)&arguments); return 0; }
Vous ne pouvez pas! À tout le moins, vous devrez ajouter un deuxième paramètre à la fonction, qui indique vers quoi le
void*
pointe.... et si vous devez quand même ajouter un deuxième paramètre, vous pouvez tout aussi bien écrire deux fonctions distinctes
func_str
etfunc_struct
et obtenir des vérifications de type au moment de la compilation.Ouais c'est pourquoi je pensais si c'était possible dans une seule fonction
Vous ne pouvez pas de manière sûre et portable. Si vous êtes assez courageux, vous pouvez essayer d'utiliser l'heuristique pour essayer de deviner si les premiers octets de mémoire ressemblent à ce à quoi vous pouvez vous attendre pour les caractères, mais je n'appellerais pas cela sûr .
Si vous voulez juste un nom commun pour les fonctions string et struct, vous pouvez utiliser une macro
_Generic
. Vous pouvez également créer des types auto-identifiables, par exemple avec des unions marquées , ce qui signifierait que vous ne pouvez pas passer une chaînechar *
brute. Tout cela pose probablement plus de problèmes que cela ne vaut la peine.