Co oznacza void * i jak go używać?


147

Dzisiaj, kiedy czytałem kod innych, zobaczyłem coś w stylu void *func(void* i);, co to void*oznacza odpowiednio dla nazwy funkcji i dla typu zmiennej?

Poza tym, kiedy musimy korzystać z tego rodzaju wskaźnika i jak z niego korzystać?


2
Jakiej książki w C używasz? Pytasz o lepszą część całego rozdziału.
cnicutar




Weź przykład z malloci calloc. Strona podręcznika mówi dalej: "... zwraca wskaźnik do przydzielonej pamięci, która jest odpowiednio wyrównana dla każdego wbudowanego typu danych."
automat z

Odpowiedzi:


175

Wskaźnik do voidjest „ogólnym” typem wskaźnika. A void *można przekonwertować na dowolny inny typ wskaźnika bez jawnego rzutowania. Nie można wyłuskać void *ani wykonać arytmetyki wskaźnikowej; musisz najpierw przekonwertować go na wskaźnik do pełnego typu danych.

void *jest często używany w miejscach, w których musisz mieć możliwość pracy z różnymi typami wskaźników w tym samym kodzie. Jednym z często cytowanych przykładów jest funkcja biblioteki qsort:

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

baseto adres tablicy, nmembto liczba elementów w tablicy, sizeto rozmiar każdego elementu i comparjest wskaźnikiem do funkcji, która porównuje dwa elementy tablicy. Nazywa się to tak:

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

Wyrażenia tablicy iArr, dArri lArrsą pośrednio przekształcony z typów tablicowe do rodzaju wskaźnika w wywołania funkcji, a każda z nich jest w sposób dorozumiany przekształcony z „wskaźnika do int/ double/ long” do „do wskaźnika void”.

Funkcje porównawcze wyglądałyby mniej więcej tak:

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

Akceptując void *, qsortmożna pracować z tablicami dowolnego typu.

Wadą używania void *jest to, że wyrzucasz typ bezpieczeństwa przez okno i zbliżasz się do ruchu. Nie ma nic, co mogłoby Cię chronić przed użyciem niewłaściwej procedury porównywania:

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

compareIntoczekuje, że jego argumenty będą wskazywać na ints, ale w rzeczywistości pracuje z doubles. Nie ma możliwości wychwycenia tego problemu w czasie kompilacji; po prostu skończysz z nieprawidłową tablicą.


5
W rzeczywistości nie ma gwarancji, że a void*można rzutować na wskaźnik funkcji. Ale jeśli chodzi o wskaźniki danych, to, co powiedziałeś, jest aktualne.
Vatine,

Zanim były dostępne wskaźniki void, zamiast tego używano znaku „char *”. Ale pustka jest lepsza, ponieważ w rzeczywistości nie można jej użyć do bezpośredniej zmiany.
user50619

22

Użycie void * oznacza, że ​​funkcja może przyjąć wskaźnik, który nie musi być określonego typu. Na przykład w funkcjach gniazd masz

send(void * pData, int nLength)

oznacza to, że można to nazwać na przykład na wiele sposobów

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

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

Więc to prawie jak typy ogólne w innych językach, ale bez sprawdzania typu, prawda?
Skrzydłowy Sendon

3
Przypuszczam, że byłoby podobnie, jednak ponieważ nie ma sprawdzania typów, popełnienie błędu może spowodować bardzo dziwne wyniki lub spowodować całkowitą awarię programu.
TheSteve

7

C jest pod tym względem niezwykły. Można powiedzieć, że voidnicość void*jest wszystkim (może być wszystkim).

Tylko ten mały *robi różnicę.

Rene zwrócił na to uwagę. A void *to wskaźnik do jakiejś lokacji. To, jak „zinterpretować”, pozostawia się użytkownikowi.

Tylko w ten sposób można mieć nieprzezroczyste typy w C. Bardzo wyraźne przykłady można znaleźć np. W bibliotekach glib lub ogólnej strukturze danych. Jest on bardzo szczegółowo opisany w rozdziale „Interfejsy C i implementacje”.

Proponuję przeczytać cały rozdział i spróbować zrozumieć pojęcie wskaźnika, aby go „zdobyć”.


5
void*

jest „wskaźnikiem do pamięci bez założeń, jaki typ jest tam przechowywany”. Możesz na przykład użyć, jeśli chcesz przekazać argument do funkcji, a argument ten może być kilku typów, aw przypadku funkcji obsługujesz każdy typ.


3

Możesz rzucić okiem na ten artykuł o wskaźnikach http://www.cplusplus.com/doc/tutorial/pointers/ i przeczytać rozdział: void pointers .

Działa to również w języku C.

Void typ wskaźnika to specjalny typ wskaźnika. W C ++ void reprezentuje brak typu, więc void wskaźniki są wskaźnikami wskazującymi na wartość, która nie ma typu (a zatem także nieokreśloną długość i nieokreślone właściwości dereference).

Dzięki temu wskaźniki void mogą wskazywać na dowolny typ danych, od wartości całkowitej lub liczby zmiennoprzecinkowej do ciągu znaków. Ale w zamian mają wielkie ograniczenie: wskazywane przez nie dane nie mogą być bezpośrednio dereferencjonowane (co jest logiczne, ponieważ nie mamy typu, do którego można by się odwołać) iz tego powodu zawsze będziemy musieli rzutować adres we wskaźniku pustki na inny typ wskaźnika, który wskazuje na konkretny typ danych przed wyłuskiwaniem go.


3

Wskaźnik void jest znany jako wskaźnik ogólny. Chciałbym wyjaśnić na przykładowym scenariuszu pthread.

Funkcja wątku będzie miała prototyp jako

void *(*start_routine)(void*)

Projektanci pthread API rozważali argument i zwracali wartości funkcji wątku. Jeśli te rzeczy są generyczne, możemy wpisać rzut na void * podczas wysyłania jako argument. podobnie wartość zwracaną można pobrać z void * (ale nigdy nie użyłem wartości zwracanych z funkcji wątku).

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

Po co dzwonić pthread_exit(NULL);zamiast return 0;na końcu głównego?
Seabass77

1
@ Seabass77 Proszę odnieść się do stackoverflow.com/questions/3559463/…
Jeyaram

1

a void*jest wskaźnikiem, ale typ tego, na co wskazuje, jest nieokreślony. Kiedy przekazujesz void wskaźnik do funkcji, będziesz musiał wiedzieć, jaki był jej typ, aby rzutować go z powrotem na ten poprawny typ później w funkcji, aby z niej skorzystać. Zobaczysz przykłady, pthreadsktóre wykorzystują funkcje z dokładnie takim prototypem w Twoim przykładzie, które są używane jako funkcja wątku. Następnie możesz użyć void*argumentu jako wskaźnika do wybranego przez siebie ogólnego typu danych, a następnie rzutować go z powrotem na ten typ w celu użycia w funkcji wątku. Musisz jednak zachować ostrożność, używając wskaźników void, ponieważ jeśli nie zwrócisz uwagi na wskaźnik jego prawdziwego typu, możesz skończyć z różnego rodzaju problemami.


1

Standard C11 (n1570) §6.2.2.3 al1 p55 mówi:

Wskaźnik do voidmożna przekonwertować na lub ze wskaźnika do dowolnego typu obiektu. Wskaźnik do dowolnego typu obiektu może zostać przekonwertowany na wskaźnik do void iz powrotem; wynik będzie równy pierwotnemu wskaźnikowi.

Możesz użyć tego ogólnego wskaźnika do przechowywania wskaźnika do dowolnego typu obiektu, ale nie możesz używać na nim zwykłych operacji arytmetycznych i nie możesz go uszanować.


0

Funkcja przyjmuje wskaźnik do dowolnego typu i zwraca jeden z nich.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.