Зачем использовать указатель void для разыменования переменных типов данных?

разыменование переменной float с помощью указателя void:

#include <stdio.h>

int main() {
    float a = 7.5;
    void *vp = &a;
    printf("%f", *(float*)vp); /* Typecasting a void pointer to float for dereference */
    printf("n");
}

выход: 7.500000

разыменование переменной с помощью целочисленного указателя:

#include <stdio.h>

int main() {
    float a = 7.5;
    int *ip = &a;
    printf("%f", *(float*)ip); /* Typecasting an int pointer to float for dereference */
    printf("n");
}

выход: 7.500000

в обоих, выходы одинаковы. Какова причина для разыменования различных переменных типа данных, когда мы можем сделать это путем типизации обычного указателя?

3 ответов


A void указатель универсальный указатель, который может содержать адрес любого типа и может быть typecast для любого типа.

в первом случае программа успешно скомпилирована и запущена без каких-либо предупреждений или ошибок, потому что с помощью void указатель для преобразования из одного типа указателя в другой, а затем сохранения или приведения его к конечному типу безопасен без потери данных.

но во втором случае GCC компилятор сгенерировал предупреждение

prog.c: In function 'main':
prog.c:5:9: warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
 int *ip=&a;
     ^

компилятор clang:

warning: incompatible pointer types initializing 'int *' with an expression of type 'float *' [-Wincompatible-pointer-types]
int *ip=&a;
     ^  ~~

стандарт C11, 6.3.2.3, пункт 7:

указатель на объект или неполный тип может быть преобразован в указатель на другой объект или неполного типа. Если в результате указатель неправильно выровнен для ссылочного типа, поведения is undefined.


преобразование любого указателя данных в void* pointer и back гарантированно вернут исходный указатель.

из проекта стандарта C11 N1570:

6.3.2.3 указатели

  1. указатель void может быть преобразован в указатель на любой тип объекта. Указатель любой тип объекта может быть преобразован в указатель на void и обратно; результат должен сравнить с исходным указателем.

преобразование указателя данных в другой указатель данных, чем void* (int* в вашем примере) мая работа. Это зависит от используемого компилятора и системы вы находитесь. Не все системы могут использовать одно и то же внутреннее представление для разных типов указателей.

  1. указатель на объектный тип может быть преобразован в указатель на другой тип объекта. Если результирующий указатель не правильно выровнены 68) для ссылочного типа, поведение не определено. В противном случае при обратном преобразовании результат будет равен оригинальный указатель. когда указатель на объект преобразуется в указатель на тип символа, результат указывает на самый низкий адресный байт объекта. Последовательные приращения результат, вплоть до размера объекта, дает указатели на оставшиеся байты объекта.

этот отличается от сглаживание строгие правила.

float a = 7.5;    
int *ip=&a;
int i = *ip; // Dereferenced using invalid type

код выше нарушает строгое правило сглаживания, поскольку разыменованный тип не совпадает с исходным типом. Это приводит к неопределено поведение и всегда неверный код.


A void указатель (вроде) нетипизированные. Он может указывать на что угодно без жалоб компилятора. например, если у вас есть int переменная, вы можете безопасно создать void указатель, который указывает на это, и пройти его вокруг. например,

int x = 10;
void *p = &x 

- это хорошо, но

int x = 10;
float *p = &x; 

расстроит компилятор

это особенно полезно для функций, которые работают с несколькими типами указателей или если вы решите, что что-то во время выполнения.

однако указатели void не могут быть разыменованы (*) напрямую, потому что компилятор не знает их тип. Итак,

printf("%d\n", *p); 

сломается, если P-указатель void. Мы должны знать размер того, на что он указывает, чтобы разыменовать его, и это делается с помощью ручного типа cast (например, что вы сделали).

в вашем конкретном случае у вас есть указатель, который указывает на float, который вы вводите в float перед его печатью. Итак, вы получите то же самое выход. The void * указатель на самом деле не играет здесь большой роли.

пример того, где вам нужно void * - это malloc функция, если вы посмотрите на прототип, она возвращает void *. т. е. блок необработанной памяти. Вам нужно типизировать это как конкретный тип, прежде чем вы сможете выполнить арифметику указателя и разыменование.