Що означає void * і як ним користуватися?


147

Сьогодні, коли я читав код інших, я побачив щось подібне void *func(void* i);, що це void*означає тут для назви функції та типу змінної відповідно?

Крім того, коли нам потрібно використовувати цей вид вказівника і як ним користуватися?


2
Яку книгу C ви використовуєте? Ви просите кращу частину цілої глави.
cnicutar

1
Подивіться на stackoverflow.com/questions/692564 / ...
WDan



Візьміть приклад з mallocі calloc. Сторінка "man" продовжує говорити: "... повернути вказівник на виділену пам'ять, відповідну вирівнюванню для будь-якого вбудованого типу даних."
автомат

Відповіді:


175

Вказівник на void- це "загальний" тип вказівника. A void *може бути перетворений у будь-який інший тип вказівника без явного виступу. Ви не можете знеструмлювати void *або виконувати арифметику вказівника з ним; ви повинні спочатку перетворити його в покажчик на повний тип даних.

void *часто використовується в місцях, де вам потрібно мати можливість працювати з різними типами вказівників в одному і тому ж коді. Одним із часто цитованих прикладів є функція бібліотеки qsort:

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

base- адреса масиву, nmembце кількість елементів у масиві, sizeрозмір кожного елемента і comparє вказівником на функцію, яка порівнює два елементи масиву. Це називається так:

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

Вирази масиву iArr, dArrі lArrнеявно перетвориться з типів масивів для типів покажчиків у виклику функції, і кожен неявно перетворюються з «покажчика int/ double/ long» в «покажчик void».

Функції порівняння виглядали б приблизно так:

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

Приймаючи void *, qsortможна працювати з масивами будь-якого типу.

Недоліком використання void *є те, що ви викидаєте безпеку типу у вікно та в зустрічний рух. Нічого не захистить вас від використання неправильної процедури порівняння:

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

compareIntочікує, що його аргументи будуть вказувати на ints, але насправді працює з doubles. Неможливо знайти цю проблему під час компіляції; ти просто закінчишся з помилковим масивом.


5
Насправді не гарантується, що а void*може бути передано покажчик функції. Але для покажчиків даних те, що ви сказали, справедливо.
Ватін

До появи пустових покажчиків замість цього було використано "char *". Але порожнеча є кращою, оскільки вона насправді не може бути використана для прямого зміни.
user50619

22

Використання void * означає, що функція може приймати вказівник, який не повинен бути конкретного типу. Наприклад, у сокетних функціях у вас є

send(void * pData, int nLength)

це означає, що ви можете назвати це багатьма способами, наприклад

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

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

Тож це дуже схоже на дженерики іншими мовами, але без перевірки типу, правда?
Вінгер Сендон

3
Я припускаю, що це буде подібним, однак, оскільки немає перевірки типу, помилка може призвести до дуже дивних результатів або призвести до кращого збою програми.
TheSteve

7

З цього питання чудовий. Можна сказати void, що небуття void*є все (може бути все).

Саме ця крихітна *істотна зміна.

Рене вказав на це. A void *- вказівник на якесь місце. Що існує як "інтерпретувати", залишається користувачеві.

Це єдиний спосіб мати непрозорі типи у C. Дуже помітні приклади можна знайти, наприклад, у бібліотеках glib або загальних структурах даних. Це розглядається дуже докладно в "C інтерфейсах та реалізаціях".

Я пропоную вам прочитати повний розділ і спробувати зрозуміти поняття вказівника, щоб "отримати його".


5
void*

"покажчик пам'яті без припущень, який тип зберігається". Наприклад, ви можете використовувати, якщо ви хочете передати аргумент функції, і цей аргумент може бути декількох типів, а за функцією ви будете обробляти кожен тип.


3

Ви можете ознайомитись із цією статтею про покажчики http://www.cplusplus.com/doc/tutorial/pointers/ та прочитати розділ: недійсні покажчики .

Це також працює для мови С.

Недійсний тип вказівника - це особливий тип вказівника. У C ++ void представляє відсутність типу, тому пусті покажчики - це покажчики, які вказують на значення, яке не має типу (і, таким чином, також невизначеної довжини та невизначених властивостей відміни).

Це дозволяє недійсним покажчикам вказувати на будь-який тип даних, від цілого значення чи поплавця до рядка символів. Але в обмін вони мають велике обмеження: дані, вказані ними, не можуть бути безпосередньо відреферентовані (що логічно, оскільки ми не маємо виду перенаправлення), і тому нам завжди доведеться вводити адресу в недійсний вказівник на деякий інший тип вказівника, який вказує на конкретний тип даних, перш ніж його віднести.


3

Недійсний покажчик відомий як загальний покажчик. Я хотів би пояснити з прикладом сценарію pthread.

Функція потоку буде мати прототип як

void *(*start_routine)(void*)

Дизайнери API pthread розглядали аргументацію та зворотні значення функції потоку. Якщо ця річ зроблена загальною, ми можемо набрати cast для void *, надсилаючи як аргумент. аналогічно повернене значення можна отримати з void * (Але я ніколи не використовував значення повернення з функції потоку).

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

Чому дзвонити, pthread_exit(NULL);а return 0;не в кінці основного?
Seabass77

1
@ Seabass77 ласка , зверніться stackoverflow.com/questions/3559463 / ...
Jeyaram

1

a void*- покажчик, але тип того, на що він вказує, не визначено. Коли ви передаєте недійсний покажчик функції, вам потрібно буде знати, який був її тип, щоб потім повернути його до правильного типу пізніше у функції, щоб використовувати його. Ви побачите приклади, в pthreadsяких використовуються функції з саме прототипом у вашому прикладі, які використовуються як функція потоку. Потім ви можете використовувати void*аргумент як вказівник на загальний тип даних на ваш вибір, а потім передати його тому типу, щоб використовувати його у своїй функції потоку. Вам потрібно бути обережними при використанні покажчиків недійсних даних, хоча, якщо тільки ви не повернетесь до вказівника його справжнього типу, у вас можуть виникнути всілякі проблеми.


1

Стандарт C11 (n1570) §6.2.2.3 al1 p55 говорить:

Вказівник, який voidможе бути перетворений на або вказівник на будь-який тип об'єкта. Вказівник на будь-який тип об'єкта може бути перетворений у вказівник, щоб знову втратити чи повернути назад; результат порівнюється рівним вихідному покажчику.

Ви можете використовувати цей загальний покажчик для зберігання вказівника на будь-який тип об'єкта, але ви не можете використовувати звичайні арифметичні операції з ним і ви не можете його визначити.


0

Функція приймає покажчик на довільний тип і повертає один такий.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.