Comment vérifier si un pointeur void (void *) est l'un des deux types de données?

10

J'écris une fonction où j'aimerais accepter 2 type s de paramètres.

  • UNE string (char *)
  • UNE structure où il y aura n nombre d'éléments.

Et pour y parvenir, je pense utiliser un void *type de paramètre simple . Mais je ne sais pas comment vérifier si le paramètre est d'un type ou d'un autre, en toute sécurité.

localhost
la source
10
Tu ne peux pas! À tout le moins, vous devrez ajouter un deuxième paramètre à la fonction, qui indique vers quoi void*pointe.
Adrian Mole
4
... et si vous devez ajouter un second paramètre de toute façon, vous pourriez tout aussi bien écrire deux fonctions distinctes func_stret func_structet obtenir des contrôles de type à la compilation.
M Oehm
Oui, c'est pourquoi je pensais que c'était possible dans une seule fonction
localhost
1
Vous ne pouvez pas le faire de manière sûre et portable. Si vous êtes assez courageux, vous pouvez essayer d'utiliser des heuristiques pour essayer de deviner si les premiers octets de mémoire ressemblent à ce que vous pourriez attendre des caractères, mais je n'appellerais pas cela sûr .
Serge Ballesta
Si vous voulez juste un nom commun pour les fonctions chaîne et struct, vous pouvez utiliser une _Genericmacro. Vous pouvez également créer des types auto-identifiables, par exemple avec des unions balisées , ce qui signifie que vous ne pouvez pas transmettre une char *chaîne brute . Tout cela est probablement plus difficile que ça en vaut la peine.
M Oehm

Réponses:

12

La traduction de void*est
"Cher compilateur, ceci est un pointeur, et il n'y a pas d'informations supplémentaires 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 pourriez avoir oubliées.
Mais dans ce cas particulier, vous savez mieux ou avez besoin de mieux savoir. Dans tous les cas, void*les informations sont disponibles autrement, mais uniquement pour le programmeur, qui "arrive à savoir". Le programmeur doit donc fournir les informations au compilateur - ou mieux au programme en cours d'exécution, car le seul avantage void*a 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 à connaître" (par exemple, pour chaque type possible, il existe une fonction distincte, quelle que soit la fonction appelée, cela implique le type).

Donc, à la fin, void*ne contient pas les informations de type.
De nombreux programmeurs comprennent mal ceci: "Je n'ai pas besoin de connaître les informations de type".
Mais l'inverse est vrai, l'utilisation de void* augmente la responsabilité du programmeur de garder la trace des informations de type et de les fournir de manière appropriée au programme / compilateur.

Yunnosch
la source
De plus, le compilateur sait réellement quel est le type de données visé. Donc, si vous sautez dans une void*fonction, convertissez le mauvais type, puis dé-référencez les données ... alors toutes sortes de comportements non définis sont invoqués.
Lundin
5

void*sont un peu 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 remarqué, vous perdez également les informations de type, ce qui signifie que vous devrez faire glisser des encombrants enumavec le void*.

Au lieu de cela, vous devez utiliser C11 _Genericqui peut vérifier les types au moment de la compilation et ajouter une sécurité de type. Exemple:

#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); 
}

N'oubliez pas de fournir des constversions qualifiées ( ) 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:

#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) 

Si vous essayez quelque chose comme int x; func(x);vous obtiendrez le message du compilateur "x: incorrect type".

Lundin
la source