Apa yang dimaksud dengan void * dan bagaimana menggunakannya?

147

Hari ini ketika saya membaca kode orang lain, saya melihat sesuatu seperti void *func(void* i);, apa artinya inivoid* artinya di sini untuk nama fungsi dan tipe variabel, masing-masing?

Selain itu, kapan kita perlu menggunakan pointer semacam ini dan bagaimana menggunakannya?

OneZero
sumber
2
Buku C apa yang Anda gunakan? Anda meminta bagian yang lebih baik dari seluruh bab.
cnicutar
Jawabannya
Ambil petunjuk dari mallocdan calloc. Halaman manual selanjutnya mengatakan: "... kembalikan pointer ke memori yang dialokasikan, yang sesuai dengan jenis data bawaan apa pun."
otomat

Jawaban:

175

Pointer ke voidadalah tipe pointer "generik". A void *dapat dikonversikan ke jenis penunjuk lain apa pun tanpa gips eksplisit. Anda tidak dapat melakukan dereferensi void *atau melakukan aritmatika pointer dengan itu; Anda harus mengonversinya menjadi pointer ke tipe data yang lengkap terlebih dahulu.

void *sering digunakan di tempat-tempat di mana Anda harus dapat bekerja dengan tipe pointer yang berbeda dalam kode yang sama. Salah satu contoh yang sering dikutip adalah fungsi perpustakaan qsort:

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

baseadalah alamat dari suatu array, nmembadalah jumlah elemen dalam array, sizeadalah ukuran dari setiap elemen, dan comparmerupakan penunjuk ke fungsi yang membandingkan dua elemen dari array. Disebut demikian:

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

Ekspresi Array iArr, dArrdan lArrsecara implisit dikonversi dari jenis array ke jenis pointer dalam panggilan fungsi, dan masing-masing secara implisit dikonversi dari "pointer ke int/ double/ long" untuk "pointer ke void".

Fungsi perbandingan akan terlihat seperti:

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

Dengan menerima void *, qsortdapat bekerja dengan array jenis apa pun.

Kerugian menggunakan void *adalah bahwa Anda membuang jenis keselamatan keluar jendela dan masuk lalu lintas yang akan datang. Tidak ada yang melindungi Anda dari menggunakan rutin perbandingan yang salah:

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

compareIntmengharapkan argumennya menunjuk ke ints, tetapi sebenarnya bekerja dengan doubles. Tidak ada cara untuk menangkap masalah ini pada waktu kompilasi; Anda hanya akan berakhir dengan array yang gagal.

John Bode
sumber
5
Ini sebenarnya tidak dijamin bahwa void*dapat dilemparkan ke pointer fungsi. Tetapi untuk data pointer apa yang Anda katakan berlaku.
Vatine
Sebelum void pointers tersedia, "char *" digunakan. Tetapi void lebih baik karena sebenarnya tidak dapat digunakan untuk mengubah apa pun secara langsung.
user50619
22

Menggunakan void * berarti bahwa fungsi tersebut dapat mengambil pointer yang tidak perlu jenis tertentu. Misalnya, dalam fungsi soket, Anda punya

send(void * pData, int nLength)

ini artinya Anda dapat menyebutnya dengan banyak cara, misalnya

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

POINT p;
p.x = 1;
p.y = 2;
send(&p, sizeof(POINT));
TheSteve
sumber
Jadi itu hampir sama dengan generik dalam bahasa lain, tetapi tanpa pengecekan tipe, kan?
Sayap Sendon
3
Saya kira itu akan serupa, namun karena tidak ada pemeriksaan jenis, membuat kesalahan dapat menyebabkan hasil yang sangat aneh atau menyebabkan program langsung macet.
TheSteve
7

C luar biasa dalam hal ini. Yang bisa dikatakan voidadalah ketiadaan void*adalah segalanya (bisa jadi segalanya).

Hanya kecil ini *yang membuat perbedaan.

Rene telah menunjukkannya. A void *adalah Pointer ke beberapa lokasi. Apa yang ada bagaimana "menafsirkan" diserahkan kepada pengguna.

Ini satu-satunya cara untuk memiliki jenis buram dalam C. Contoh yang sangat menonjol dapat ditemukan misalnya di pustaka struktur data umum atau glib. Itu diperlakukan sangat rinci dalam "Antarmuka C dan implementasi".

Saya sarankan Anda membaca bab lengkap dan mencoba memahami konsep pointer untuk "mendapatkannya".

Friedrich
sumber
5
void*

adalah 'penunjuk ke memori tanpa asumsi jenis apa yang disimpan'. Anda dapat menggunakan, misalnya, jika Anda ingin meneruskan argumen ke fungsi dan argumen ini bisa dari beberapa jenis dan dalam fungsi Anda akan menangani masing-masing jenis.

René Kolařík
sumber
3

Anda dapat melihat artikel ini tentang pointer http://www.cplusplus.com/doc/tutorial/pointers/ dan membaca bab: void pointers .

Ini juga berfungsi untuk bahasa C.

Jenis batal dari pointer adalah tipe khusus dari pointer. Dalam C ++, void mewakili tidak adanya tipe, jadi void pointer adalah pointer yang menunjuk ke nilai yang tidak memiliki tipe (dan dengan demikian juga panjang yang tidak ditentukan dan sifat dereferensi yang tidak ditentukan).

Ini memungkinkan void pointer untuk menunjuk ke tipe data apa pun, dari nilai integer atau float ke serangkaian karakter. Tetapi sebagai gantinya mereka memiliki batasan yang besar: data yang ditunjukkan oleh mereka tidak dapat secara langsung direferensikan (yang logis, karena kita tidak memiliki tipe untuk dereferensi ke), dan untuk alasan itu kita akan selalu harus memasukkan alamat dalam void pointer ke beberapa tipe pointer lain yang menunjuk ke tipe data konkret sebelum mendereferensinya.

AG
sumber
3

Void pointer dikenal sebagai pointer generik. Saya ingin menjelaskan dengan skenario sampel pthread.

Fungsi utas akan memiliki prototipe sebagai

void *(*start_routine)(void*)

Perancang API pthread mempertimbangkan argumen dan mengembalikan nilai fungsi utas. Jika hal itu dibuat generik, kita dapat mengetikkan cast untuk membatalkan * saat mengirim sebagai argumen. demikian pula nilai kembali dapat diambil dari void * (Tapi saya tidak pernah menggunakan nilai balik dari fungsi utas).

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
sumber
Mengapa menelepon pthread_exit(NULL);daripada return 0;di akhir main?
Seabass77
1
@ Seabass77 Silakan merujuk stackoverflow.com/questions/3559463/…
Jeyaram
1

a void*adalah pointer, tetapi tipe yang ditunjukkannya tidak ditentukan. Saat Anda melewatkan pointer kosong ke suatu fungsi, Anda harus tahu apa tipenya untuk mengembalikannya ke tipe yang benar nanti dalam fungsi untuk menggunakannya. Anda akan melihat contoh dalam pthreadsfungsi yang menggunakan persis prototipe dalam contoh Anda yang digunakan sebagai fungsi utas. Anda kemudian dapat menggunakan void*argumen sebagai pointer ke tipe data generik yang Anda pilih dan kemudian melemparkannya kembali ke tipe itu untuk digunakan dalam fungsi utas Anda. Anda harus berhati-hati saat menggunakan void pointer meskipun kecuali jika Anda case kembali ke pointer dari tipe yang sebenarnya, Anda dapat berakhir dengan segala macam masalah.

ahli matematika1975
sumber
1

Standar C11 (n1570) §6.2.2.3 al1 p55 mengatakan:

Pointer ke voiddapat dikonversi ke atau dari pointer ke tipe objek apa pun. Pointer ke semua jenis objek dapat dikonversi ke pointer untuk membatalkan dan kembali lagi; hasilnya harus sama dengan pointer asli.

Anda bisa menggunakan pointer generik ini untuk menyimpan pointer ke semua tipe objek, tetapi Anda tidak bisa menggunakan operasi aritmatika biasa dengannya dan Anda tidak bisa menghormatinya.

md5
sumber
0

Fungsi mengambil pointer ke tipe arbitrer dan mengembalikannya.

glglgl
sumber