10
votes

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

J'écris une fonction où je voudrais accepter 2 type 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 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é.


5 commentaires

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 et func_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îne char * brute. Tout cela pose probablement plus de problèmes que cela ne vaut la peine.


3 Réponses :


13
votes

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.


1 commentaires

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.



5
votes

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" .


0 commentaires

0
votes

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


0 commentaires