Come verificare se un puntatore vuoto (vuoto *) è uno dei due tipi di dati?


10

Sto scrivendo una funzione in cui vorrei accettare 2 types di parametri.

  • A string(carattere *)
  • A structuredove ci saranno n numero di elementi.

E per raggiungere questo obiettivo sto pensando di usare un semplice void *tipo di parametro. Ma non so come verificare se il parametro è di un tipo o l'altro, in modo sicuro.


10
Non puoi! Per lo meno, dovrai aggiungere un secondo parametro alla funzione, che indica a cosa void*punta.
Adrian Mole,

4
... e se è necessario aggiungere un secondo parametro in ogni caso, si potrebbe altrettanto bene scrivere due funzioni separate func_stred func_structe ottenere controlli di tipo in fase di compilazione.
M Oehm,

Sì, è per questo che pensavo che fosse possibile in una sola funzione
localhost l'

1
Non puoi in modo sicuro e portatile. Se sei abbastanza coraggioso, puoi provare a usare l'euristica per provare a indovinare se i primi byte di memoria assomigliano a ciò che potresti aspettarti dai personaggi, ma non lo definirei sicuro .
Serge Ballesta,

Se si desidera solo un nome comune per le funzioni stringa e struct, è possibile utilizzare una _Genericmacro. È inoltre possibile creare tipi di identificazione automatica, ad esempio con i sindacati con tag , il che significa che non è possibile passare una char *stringa non elaborata. Tutto ciò è probabilmente più problemi di quanti ne valga la pena.
M Oehm,

Risposte:


12

La traduzione di void*è
"Caro compilatore, questo è un puntatore, e non ci sono ulteriori informazioni su questo.".

Di solito il compilatore conosce meglio di te (il programmatore), a causa delle informazioni che ha ricevuto in precedenza e che ricorda ancora e che potresti aver dimenticato.
Ma in questo caso speciale, conosci meglio o devi conoscere meglio. In tutti i casi void*le informazioni sono disponibili diversamente, ma solo al programmatore, che "capita di saperlo". Il programmatore deve fornire le informazioni al compilatore - o meglio al programma in esecuzione, perché l'unico vantaggio che void*ha è che le informazioni possono cambiare durante il runtime.
Di solito ciò viene fatto fornendo informazioni tramite parametri aggiuntivi a funzioni, a volte tramite contesto, ovvero il programma "capisce" (ad es. Per ogni tipo possibile esiste una funzione separata, qualunque funzione venga chiamata implica il tipo).

Quindi alla fine void*non contiene le informazioni sul tipo.
Molti programmatori lo fraintendono come "Non ho bisogno di conoscere le informazioni sul tipo".
Ma è vero il contrario, l'uso di void* aumenta la responsabilità del programmatore di tenere traccia delle informazioni sul tipo e fornirle in modo appropriato al programma / compilatore.


Inoltre, il compilatore in realtà sa quale sia il tipo di dati puntato. Quindi, se si void*passa a una funzione, si esegue il cast nel tipo sbagliato, quindi si rimuove il riferimento ai dati ... quindi viene invocato ogni tipo di comportamento indefinito.
Lundin

5

void*sono un po 'deprecati per la programmazione generica, non ci sono molte situazioni in cui dovresti usarli al giorno d'oggi. Sono pericolosi perché portano a una sicurezza di tipo inesistente. E come hai notato, perdi anche le informazioni sul tipo, il che significa che dovresti trascinare un po 'ingombrante enuminsieme a void*.

Invece dovresti usare C11 _Genericche può controllare i tipi in fase di compilazione e aggiungere la sicurezza dei tipi. Esempio:

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

Ricorda di fornire constversioni qualificate ( ) di tutti i tipi che desideri supportare.


Se desideri errori di compilazione migliori quando il chiamante passa il tipo sbagliato, puoi aggiungere un'asserzione statica:

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

Se provi qualcosa del genere int x; func(x);otterrai il messaggio del compilatore "x: incorrect type".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.