Que signifie void * et comment l'utiliser?

147

Aujourd'hui, quand je lisais le code des autres, j'ai vu quelque chose comme void *func(void* i);, qu'est-ce que ça faitvoid* que signifie ici pour le nom de la fonction et pour le type de variable, respectivement?

De plus, quand devons-nous utiliser ce type de pointeur et comment l'utiliser?

OneZero
la source
2
Quel livre C utilisez-vous? Vous demandez la meilleure partie d'un chapitre entier.
cnicutar
1
Jetez un œil à stackoverflow.com/questions/692564/…
WDan
Inspirez-vous de mallocet calloc. La page de manuel continue en disant: "... renvoie un pointeur vers la mémoire allouée, qui est correctement alignée pour tout type de données intégré."
automate

Réponses:

175

Un pointeur vers voidest un type de pointeur "générique". A void *peut être converti en tout autre type de pointeur sans conversion explicite. Vous ne pouvez pas déréférencer un void *ou faire de l'arithmétique de pointeur avec lui; vous devez d'abord le convertir en pointeur vers un type de données complet.

void *est souvent utilisé dans les endroits où vous devez pouvoir travailler avec différents types de pointeurs dans le même code. Un exemple couramment cité est la fonction de bibliothèque qsort:

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

baseest l'adresse d'un tableau, nmembest le nombre d'éléments dans le tableau, sizeest la taille de chaque élément et comparest un pointeur vers une fonction qui compare deux éléments du tableau. Il s'appelle ainsi:

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

Les expressions du tableau iArr, dArret lArrsont convertis implicitement des types de tableau à des types de pointeur dans l'appel de fonction, et chacun est implicitement converti de « pointeur vers int/ double/ long» à « pointeur void».

Les fonctions de comparaison ressembleraient à quelque chose comme:

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

En acceptant void *, qsortpeut travailler avec des tableaux de tout type.

L'inconvénient de l'utilisation void *est que vous jetez la sécurité de type par la fenêtre et dans le trafic venant en sens inverse. Rien ne vous empêche d'utiliser la mauvaise routine de comparaison:

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

compareInts'attend à ce que ses arguments pointent vers ints, mais travaille en fait avec l' doubleart. Il n'y a aucun moyen d'attraper ce problème au moment de la compilation; vous vous retrouverez juste avec un tableau mal trié.

John Bode
la source
5
Il n'est en fait pas garanti que a void*puisse être converti en un pointeur de fonction. Mais pour les pointeurs de données, ce que vous avez dit est valable.
Vatine
Avant que les pointeurs void ne soient disponibles, "char *" était utilisé à la place. Mais le vide est meilleur car il ne peut en fait être utilisé pour modifier quoi que ce soit directement.
user50619
22

L'utilisation d'un void * signifie que la fonction peut prendre un pointeur qui n'a pas besoin d'être d'un type spécifique. Par exemple, dans les fonctions de socket, vous avez

send(void * pData, int nLength)

cela signifie que vous pouvez l'appeler de plusieurs façons, par exemple

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

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));
TheSteve
la source
C'est donc un peu comme les génériques dans d'autres langues, mais sans vérification de type, non?
Ailier Sendon
3
Je suppose que ce serait similaire, mais comme il n'y a pas de vérification de type, faire une erreur peut provoquer des résultats très étranges ou provoquer un crash pur et simple du programme.
TheSteve
7

C est remarquable à cet égard. On peut dire que le voidnéant void*est tout (peut être tout).

C'est juste ce minuscule *qui fait la différence.

René l'a signalé. A void *est un pointeur vers un emplacement. Ce qu'il y a comment «interpréter» est laissé à l'utilisateur.

C'est la seule façon d'avoir des types opaques en C. Des exemples très importants peuvent être trouvés par exemple dans glib ou dans des bibliothèques de structures de données générales. Il est traité de manière très détaillée dans "Interfaces C et implémentations".

Je vous suggère de lire le chapitre complet et d'essayer de comprendre le concept d'un pointeur pour "l'obtenir".

Friedrich
la source
5
void*

est un «pointeur vers la mémoire sans hypothèse sur le type de stockage». Vous pouvez utiliser, par exemple, si vous voulez passer un argument à la fonction et cet argument peut être de plusieurs types et en fonction, vous gérerez chaque type.

René Kolařík
la source
3

Vous pouvez consulter cet article sur les pointeurs http://www.cplusplus.com/doc/tutorial/pointers/ et lire le chapitre: void pointers .

Cela fonctionne également pour le langage C.

Le type de pointeur void est un type spécial de pointeur. En C ++, void représente l'absence de type, donc les pointeurs void sont des pointeurs qui pointent vers une valeur qui n'a pas de type (et donc aussi une longueur indéterminée et des propriétés de déréférencement indéterminées).

Cela permet aux pointeurs void de pointer vers n'importe quel type de données, d'une valeur entière ou d'un flottant à une chaîne de caractères. Mais en échange, ils ont une grande limitation: les données pointées par eux ne peuvent pas être directement déréférencées (ce qui est logique, car nous n'avons pas de type à déréférencer), et pour cette raison, nous devrons toujours lancer l'adresse dans le pointeur vide vers un autre type de pointeur qui pointe vers un type de données concret avant de le déréférencer.

AG
la source
3

Un pointeur void est appelé pointeur générique. Je voudrais expliquer avec un exemple de scénario pthread.

La fonction thread aura le prototype comme

void *(*start_routine)(void*)

Les concepteurs de l'API pthread ont pris en compte l'argument et les valeurs de retour de la fonction de thread. Si ces éléments sont rendus génériques, nous pouvons taper cast to void * lors de l'envoi en argument. de même, la valeur de retour peut être récupérée à partir de void * (mais je n'ai jamais utilisé les valeurs de retour de la fonction de 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);
}
Jeyaram
la source
Pourquoi appeler pthread_exit(NULL);plutôt return 0;qu'à la fin de main?
Seabass77
1
@ Seabass77 Veuillez vous référer à stackoverflow.com/questions/3559463/…
Jeyaram
1

a void*est un pointeur, mais le type de ce qu'il pointe n'est pas spécifié. Lorsque vous passez un pointeur void à une fonction, vous devez savoir quel était son type afin de le renvoyer à ce type correct plus tard dans la fonction pour l'utiliser. Vous verrez des exemples pthreadsqui utilisent des fonctions avec exactement le prototype de votre exemple qui sont utilisées comme fonction de thread. Vous pouvez ensuite utiliser l' void*argument comme pointeur vers un type de données générique de votre choix, puis le renvoyer vers ce type pour l'utiliser dans votre fonction de thread. Vous devez être prudent lorsque vous utilisez des pointeurs vides, car à moins que vous ne reveniez à un pointeur de son vrai type, vous pouvez vous retrouver avec toutes sortes de problèmes.

mathématicien1975
la source
1

La norme C11 (n1570) §6.2.2.3 al1 p55 dit:

Un pointeur vers voidpeut être converti vers ou depuis un pointeur vers n'importe quel type d'objet. Un pointeur vers n'importe quel type d'objet peut être converti en un pointeur vers void et inversement; le résultat doit être comparable au pointeur d'origine.

Vous pouvez utiliser ce pointeur générique pour stocker un pointeur sur n'importe quel type d'objet, mais vous ne pouvez pas utiliser les opérations arithmétiques habituelles avec lui et vous ne pouvez pas le déférer.

md5
la source
0

La fonction prend un pointeur vers un type arbitraire et en renvoie un.

glglgl
la source