Was bedeutet void * und wie benutzt man es?

147

Als ich heute den Code anderer las, sah ich so etwas wie void *func(void* i);: Was bedeutet das void*hier für den Funktionsnamen bzw. für den Variablentyp?

Wann müssen wir diese Art von Zeiger verwenden und wie wird er verwendet?

Eine Null
quelle
2
Welches C-Buch benutzt du? Sie fragen nach dem größten Teil eines ganzen Kapitels.
cnicutar
1
Werfen
WDan
Nehmen Sie ein Stichwort von mallocund calloc. In der Manpage heißt es weiter: "... einen Zeiger auf den zugewiesenen Speicher zurückgeben, der für jeden integrierten Datentyp geeignet ausgerichtet ist."
Automat

Antworten:

175

Ein Zeiger auf voidist ein "generischer" Zeigertyp. A void *kann ohne explizite Umwandlung in einen anderen Zeigertyp konvertiert werden. Sie können eine dereferenzieren void *oder eine Zeigerarithmetik damit durchführen. Sie müssen es zuerst in einen Zeiger auf einen vollständigen Datentyp konvertieren.

void *wird häufig an Stellen verwendet, an denen Sie in der Lage sein müssen, mit verschiedenen Zeigertypen im selben Code zu arbeiten. Ein häufig genanntes Beispiel ist die Bibliotheksfunktion qsort:

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

baseist die Adresse eines Arrays, nmembist die Anzahl der Elemente im Array, sizeist die Größe jedes Elements und comparist ein Zeiger auf eine Funktion, die zwei Elemente des Arrays vergleicht. Es wird so genannt:

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

Die Array - Ausdrücke iArr, dArrund lArrimplizit von Array - Typ auf Zeiger Typen in dem Funktionsaufruf umgewandelt, und jedes ist implizit aus „Zeigern auf umgewandelt int/ double/ long“ auf „Zeiger void“.

Die Vergleichsfunktionen würden ungefähr so ​​aussehen:

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

Durch die Annahme void *, qsortkann mit Arrays jeder Art arbeiten.

Der Nachteil der Verwendung void *ist, dass Sie die Typensicherheit aus dem Fenster in den Gegenverkehr werfen. Es gibt nichts, was Sie davor schützen könnte, die falsche Vergleichsroutine zu verwenden:

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

compareInterwartet, dass seine Argumente auf ints verweisen, arbeitet aber tatsächlich mit doubles. Es gibt keine Möglichkeit, dieses Problem beim Kompilieren zu beheben. Sie werden nur mit einem falsch sortierten Array enden.

John Bode
quelle
5
Es ist eigentlich nicht garantiert, dass a void*in einen Funktionszeiger umgewandelt werden kann. Aber für Datenzeiger gilt, was Sie gesagt haben.
Vatine
Bevor leere Zeiger verfügbar waren, wurde stattdessen "char *" verwendet. Aber void ist besser, da es nicht verwendet werden kann, um etwas direkt zu ändern.
user50619
22

Die Verwendung eines Leerzeichens * bedeutet, dass die Funktion einen Zeiger annehmen kann, der kein bestimmter Typ sein muss. Zum Beispiel haben Sie in Socket-Funktionen

send(void * pData, int nLength)

Dies bedeutet, dass Sie es beispielsweise auf viele Arten aufrufen können

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

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));
TheSteve
quelle
Das ist also ziemlich ähnlich wie bei Generika in anderen Sprachen, aber ohne Typprüfung, oder?
Flügelspieler Sendon
3
Ich nehme an, es wäre ähnlich, aber da es keine Typprüfung gibt, kann ein Fehler zu sehr seltsamen Ergebnissen führen oder das Programm zum Absturz bringen.
TheSteve
7

C ist in dieser Hinsicht bemerkenswert. Man kann sagen, voidNichts void*ist alles (kann alles sein).

Es ist nur so winzig *, was den Unterschied macht.

Rene hat darauf hingewiesen. A void *ist ein Zeiger auf einen Ort. Was dort zu "interpretieren" ist, bleibt dem Benutzer überlassen.

Dies ist die einzige Möglichkeit, in C undurchsichtige Typen zu haben. Sehr prominente Beispiele finden sich z. B. in glib- oder allgemeinen Datenstrukturbibliotheken. Es wird in "C-Schnittstellen und Implementierungen" sehr detailliert behandelt.

Ich schlage vor, Sie lesen das gesamte Kapitel und versuchen, das Konzept eines Zeigers zu verstehen, um "es zu bekommen".

Friedrich
quelle
5
void*

ist ein 'Zeiger auf den Speicher ohne Annahmen, welcher Typ dort gespeichert ist'. Sie können beispielsweise verwenden, wenn Sie ein Argument an function übergeben möchten und dieses Argument von mehreren Typen sein kann. In function werden Sie jeden Typ behandeln.

René Kolařík
quelle
3

Sie können sich diesen Artikel über Zeiger http://www.cplusplus.com/doc/tutorial/pointers/ ansehen und das Kapitel lesen: Zeiger ungültig machen .

Dies funktioniert auch für die Sprache C.

Der ungültige Zeigertyp ist ein spezieller Zeigertyp. In C ++ stellt void das Fehlen eines Typs dar. Void-Zeiger sind also Zeiger, die auf einen Wert verweisen, der keinen Typ hat (und somit auch eine unbestimmte Länge und unbestimmte Dereferenzierungseigenschaften).

Auf diese Weise können ungültige Zeiger auf einen beliebigen Datentyp verweisen, von einem ganzzahligen Wert oder einem Float auf eine Zeichenfolge. Im Gegenzug haben sie jedoch eine große Einschränkung: Die Daten, auf die sie verweisen, können nicht direkt dereferenziert werden (was logisch ist, da wir keinen Typ haben, auf den wir dereferenzieren können), und aus diesem Grund müssen wir die Adresse immer in den ungültigen Zeiger auf setzen Ein anderer Zeigertyp, der auf einen konkreten Datentyp verweist, bevor er dereferenziert wird.

AG
quelle
3

Ein ungültiger Zeiger wird als generischer Zeiger bezeichnet. Ich möchte mit einem Beispiel-Pthread-Szenario erklären.

Die Thread-Funktion hat den Prototyp als

void *(*start_routine)(void*)

Die pthread-API-Designer haben die Argument- und Rückgabewerte der Thread-Funktion berücksichtigt. Wenn diese Dinge generisch gemacht werden, können wir beim Senden als Argument cast to void * eingeben. In ähnlicher Weise kann der Rückgabewert aus void * abgerufen werden (aber ich habe nie Rückgabewerte aus der Thread-Funktion verwendet).

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
quelle
Warum anrufen pthread_exit(NULL);statt return 0;am Ende von main?
Seabass77
1
@ Seabass77 Bitte beziehen Sie sich auf stackoverflow.com/questions/3559463/…
Jeyaram
1

a void*ist ein Zeiger, aber der Typ, auf den er zeigt, ist nicht angegeben. Wenn Sie einen ungültigen Zeiger an eine Funktion übergeben, müssen Sie deren Typ kennen, um ihn später in der Funktion auf den richtigen Typ zurücksetzen zu können, um ihn zu verwenden. Sie werden Beispiele sehen pthreads, die Funktionen mit genau dem Prototyp in Ihrem Beispiel verwenden, die als Thread-Funktion verwendet werden. Sie können das void*Argument dann als Zeiger auf einen generischen Datentyp Ihrer Wahl verwenden und es dann auf diesen Typ zurücksetzen, um ihn in Ihrer Thread-Funktion zu verwenden. Sie müssen jedoch vorsichtig sein, wenn Sie ungültige Zeiger verwenden. Wenn Sie jedoch nicht auf einen Zeiger seines wahren Typs zurückgreifen, können alle möglichen Probleme auftreten.

Mathematiker 1975
quelle
1

C11 Standard (n1570) §6.2.2.3 al1 p55 sagt:

Ein Zeiger auf voidkann in oder von einem Zeiger auf einen beliebigen Objekttyp konvertiert werden. Ein Zeiger auf einen beliebigen Objekttyp kann in einen Zeiger auf ungültig und wieder zurück konvertiert werden. Das Ergebnis muss mit dem ursprünglichen Zeiger verglichen werden.

Sie können diesen generischen Zeiger verwenden, um einen Zeiger auf einen beliebigen Objekttyp zu speichern, aber Sie können damit keine üblichen arithmetischen Operationen verwenden und ihn nicht zurückstellen.

md5
quelle
0

Die Funktion nimmt einen Zeiger auf einen beliebigen Typ und gibt einen solchen zurück.

glglgl
quelle