Cosa significa void * e come usarlo?


147

Oggi, mentre leggevo il codice degli altri, ho visto qualcosa del genere void *func(void* i);, cosa significa questo void*qui, rispettivamente, per il nome della funzione e per il tipo di variabile?

Inoltre, quando dobbiamo utilizzare questo tipo di puntatore e come usarlo?


2
Quale libro C stai usando? Stai chiedendo la parte migliore di un intero capitolo.
cnicutar,




Prendi spunto da malloce calloc. La pagina man continua dicendo: "... restituisce un puntatore alla memoria allocata, che è opportunamente allineata per qualsiasi tipo di dati incorporato."
automa,

Risposte:


175

Un puntatore a voidè un tipo di puntatore "generico". A void *può essere convertito in qualsiasi altro tipo di puntatore senza un cast esplicito. Non puoi dereferenziare void *o eseguire l'aritmetica del puntatore con esso; devi prima convertirlo in un puntatore in un tipo di dati completo.

void *viene spesso utilizzato in luoghi in cui è necessario poter lavorare con diversi tipi di puntatore nello stesso codice. Un esempio comunemente citato è la funzione di libreria qsort:

void qsort(void *base, size_t nmemb, size_t size, 
           int (*compar)(const void *, const void *));

baseè l'indirizzo di un array, nmembè il numero di elementi nell'array, sizeè la dimensione di ciascun elemento ed comparè un puntatore a una funzione che confronta due elementi dell'array. Si chiama così:

int iArr[10];
double dArr[30];
long lArr[50];
...
qsort(iArr, sizeof iArr/sizeof iArr[0], sizeof iArr[0], compareInt);
qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareDouble);
qsort(lArr, sizeof lArr/sizeof lArr[0], sizeof lArr[0], compareLong);

Le espressioni di matrice iArr, dArre lArrsono implicitamente convertiti da tipi array di tipi di puntatore nella chiamata di funzione, e ciascuno è implicitamente convertito da "puntatore a int/ double/ long" a "puntatore void".

Le funzioni di confronto sarebbero simili a:

int compareInt(const void *lhs, const void *rhs)
{
  const int *x = lhs;  // convert void * to int * by assignment
  const int *y = rhs;

  if (*x > *y) return 1;
  if (*x == *y) return 0;
  return -1;
}

Accettando void *, qsortpuò lavorare con array di qualsiasi tipo.

Lo svantaggio dell'utilizzo void *è che si lancia la sicurezza del tipo fuori dalla finestra e nel traffico in arrivo. Non c'è nulla che ti protegga dall'utilizzare la routine di confronto sbagliata:

qsort(dArr, sizeof dArr/sizeof dArr[0], sizeof dArr[0], compareInt);

compareIntsi aspetta che i suoi argomenti puntino a ints, ma in realtà sta lavorando con doubles. Non c'è modo di risolvere questo problema in fase di compilazione; ti ritroverai con un array distorto.


5
In realtà non è garantito che a void*possa essere lanciato su un puntatore a funzione. Ma per i puntatori ai dati vale quello che hai detto.
Vatine,

Prima che fossero disponibili i puntatori vuoti, veniva invece utilizzato "char *". Ma il vuoto è migliore in quanto non può essere effettivamente utilizzato per alterare nulla direttamente.
user50619

22

L'uso di un vuoto * significa che la funzione può prendere un puntatore che non deve essere di un tipo specifico. Ad esempio, nelle funzioni socket, hai

send(void * pData, int nLength)

questo significa che puoi chiamarlo in molti modi, per esempio

char * data = "blah";
send(data, strlen(data));

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));

Quindi è un po 'come i generici in altre lingue, ma senza controllo del tipo, giusto?
Winger Sendon,

3
Suppongo che sarebbe simile, tuttavia, poiché non esiste un controllo del tipo, commettere un errore può causare risultati molto strani o causare un arresto anomalo del programma.
TheSteve

7

C è notevole in questo senso. Si può dire che il voidnulla void*è tutto (può essere tutto).

È solo questo minuscolo *che fa la differenza.

René l'ha sottolineato. A void *è un puntatore in qualche posizione. Quello che c'è come "interpretare" è lasciato all'utente.

È l'unico modo per avere tipi opachi in C. Si possono trovare esempi molto importanti, ad esempio nelle librerie di glib o della struttura generale dei dati. È trattato in modo molto dettagliato in "Interfacce e implementazioni in C".

Ti consiglio di leggere il capitolo completo e provare a capire il concetto di puntatore per "ottenerlo".


5
void*

è un "puntatore alla memoria senza ipotesi di quale tipo sia memorizzato". È possibile utilizzare, ad esempio, se si desidera passare un argomento per funzionare e questo argomento può essere di diversi tipi e in funzione si gestirà ogni tipo.


3

Puoi dare un'occhiata a questo articolo sui puntatori http://www.cplusplus.com/doc/tutorial/pointers/ e leggere il capitolo: void pointers .

Questo funziona anche per il linguaggio C.

Il tipo vuoto di puntatore è un tipo speciale di puntatore. In C ++, void rappresenta l'assenza di tipo, quindi i puntatori void sono puntatori che puntano a un valore che non ha tipo (e quindi anche una lunghezza indeterminata e proprietà di dereference indeterminate).

Ciò consente ai puntatori vuoti di puntare a qualsiasi tipo di dati, da un valore intero o un float a una stringa di caratteri. Ma in cambio hanno una grande limitazione: i dati puntati da loro non possono essere direttamente dereferenziati (il che è logico, dal momento che non abbiamo alcun tipo a cui fare riferimento), e per questo motivo dovremo sempre lanciare l'indirizzo nel puntatore vuoto qualche altro tipo di puntatore che punta a un tipo di dati concreto prima di dereferenziarlo.


3

Un puntatore vuoto è noto come puntatore generico. Vorrei spiegare con uno scenario pthread di esempio.

La funzione thread avrà il prototipo come

void *(*start_routine)(void*)

I progettisti dell'API pthread hanno considerato l'argomento e restituiscono i valori della funzione thread. Se queste cose sono rese generiche, possiamo digitare cast su void * durante l'invio come argomento. allo stesso modo il valore restituito può essere recuperato dal vuoto * (ma non ho mai usato i valori restituiti dalla funzione thread).

void *PrintHello(void *threadid)
{
   long tid;

   // ***Arg sent in main is retrieved   ***
   tid = (long)threadid;
   printf("Hello World! It's me, thread #%ld!\n", tid);
   pthread_exit(NULL);
}

int main (int argc, char *argv[])
{
   pthread_t threads[NUM_THREADS];
   int rc;
   long t;
   for(t=0; t<NUM_THREADS; t++){
      //*** t will be type cast to void* and send as argument.
      rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);   
      if (rc){
         printf("ERROR; return code from pthread_create() is %d\n", rc);
         exit(-1);
      }
   }    
   /* Last thing that main() should do */
   pthread_exit(NULL);
}

Perché chiamare pthread_exit(NULL);invece che return 0;alla fine del main?
Seabass77,

1
@ Seabass77 prega di fare riferimento stackoverflow.com/questions/3559463/...
Jeyaram

1

a void*è un puntatore, ma il tipo di ciò a cui punta non è specificato. Quando si passa un puntatore vuoto a una funzione, sarà necessario sapere quale sia stato il suo tipo per poterlo ricondurre a quel tipo corretto in seguito nella funzione per utilizzarlo. Vedrai degli esempi pthreadsche usano funzioni con esattamente il prototipo nel tuo esempio che sono usate come funzione thread. È quindi possibile utilizzare l' void*argomento come puntatore a un tipo di dati generico di propria scelta e quindi eseguirne il cast su quel tipo da utilizzare all'interno della funzione thread. Devi stare attento quando usi i puntatori vuoti anche se a meno che tu non torni a un puntatore del suo vero tipo, puoi finire con tutti i tipi di problemi.


1

Lo standard C11 (n1570) §6.2.2.3 al1 p55 dice:

Un puntatore voidpuò essere convertito in o da un puntatore a qualsiasi tipo di oggetto. Un puntatore a qualsiasi tipo di oggetto può essere convertito in un puntatore in vuoto e viceversa; il risultato deve essere uguale al puntatore originale.

È possibile utilizzare questo puntatore generico per memorizzare un puntatore a qualsiasi tipo di oggetto, ma non è possibile utilizzare le normali operazioni aritmetiche con esso e non è possibile deferirlo.


0

La funzione prende un puntatore a un tipo arbitrario e ne restituisce uno.


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.