为啥要使用 void 指针来取消引用数据类型的变量?

Posted

技术标签:

【中文标题】为啥要使用 void 指针来取消引用数据类型的变量?【英文标题】:Why use a void pointer for dereferencing variables of datatypes?为什么要使用 void 指针来取消引用数据类型的变量? 【发布时间】:2018-02-03 03:04:57 【问题描述】:

使用 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

两者的输出是相同的。当我们能够通过类型转换普通指针来解引用不同数据类型变量的原因是什么?

【问题讨论】:

使用int *ip=&amp;a; 你打破了严格的别名。你说ip 是指向某物的指针,但它实际上是指向不同事物的指针。使用void * 的通用指针没有类型,它只是一个“指向某物的指针,无论它是什么”。 void main 必须是 int main @Someprogrammerdude 只有在取消引用 ip 时,该代码才会破坏严格的别名。 ip 被转换回 float*,所以代码只有实现定义的指针转换。 【参考方案1】:

将任何数据指针转换为void* 指针并返回可以保证返回原始指针。

来自 C11 标准草案 N1570:

6.3.2.3 指针

    指向void 的指针可以转换为或从指向任何对象类型的指针转​​换。指向的指针 任何对象类型都可以转换为指向 void 并再次返回的指针;结果应 比较等于原始指针。

将数据指针转换为 void* 以外的其他数据指针(在您的示例中为 int*可能工作。这取决于您使用的编译器和您所在的系统。并非所有系统都可能对不同的指针类型使用相同的内部表示。

    指向对象类型的指针可以转换为指向不同对象类型的指针。如果 结果指针未正确对齐 68) 对于引用的类型,行为是 不明确的。否则,当再次转换回来时,结果将等于 原始指针。 当指向对象的指针转换为指向字符类型的指针时, 结果指向对象的最低寻址字节。的连续递增 结果,直到对象的大小,产生指向对象剩余字节的指针。

这不同于严格的别名规则

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

上面的代码违反了严格的别名规则,因为取消引用的类型与原始类型不同。这会导致未定义的行为,并且始终是无效代码。

【讨论】:

示例:假设您的平台对浮点数有严格的对齐要求,但对整数没有。因此,一个 int 指针可以表示为它在内存中的字节偏移量,但一个浮点指针可以表示为它在内存中的 index,因为假设的 FPU 以这种方式加载值更有效。在这样的系统上,将float* 转换为void* 会将底层地址表示乘以sizeof(float) 以获得字节偏移量,但将int* 转换为void* 是无操作的。直接在 int*float* 之间转换会冒 UB (6.3.2.3) 的风险; via int 会产生垃圾。【参考方案2】:

void 指针是一个通用指针,它可以保存任何类型的地址,并且可以进行类型转换 到任何类型。

在第一种情况下,程序成功编译并运行,没有任何警告或错误,因为使用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 段:

指向对象或不完整类型的指针可以转换为 指向不同对象或不完整类型的指针。如果结果 指针未正确对齐引用的类型,行为 是未定义

【讨论】:

【参考方案3】:

void 指针(在某种程度上)是无类型的。它可以指向任何东西而编译器不会抱怨。例如如果你有一个int 变量,你可以安全地创建一个指向它的void 指针并传递它。例如

int x = 10;
void *p = &x 

很好,但是

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

会扰乱编译器

这对于在多个指针类型上操作的函数或者如果您将在运行时决定什么是特别有用的。

但是,由于编译器不知道它们的类型,所以不能直接取消引用 void 指针 (*)。所以,

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

如果 p 是 void 指针,将中断。我们必须知道它指向的大小才能取消引用它,这是使用手动类型转换完成的(就像你所做的那样)。

在您的特定情况下,您有一个指向浮点数的指针,您在打印之前将其转换回浮点数。因此,您将获得相同的输出。 void * 指针在这里并没有真正发挥重要作用。

需要void * 的示例是malloc 函数,如果查看原型,它会返回void *。即一块原始内存。在进行指针算术和取消引用之前,您需要将其类型转换为具体类型。

【讨论】:

“我们必须知道大小”。不,我们必须知道类型。尺寸不够。 是的。虽然,我不确定为什么我们需要知道这一点。如果你能详细说明,我相信它会很有用。

以上是关于为啥要使用 void 指针来取消引用数据类型的变量?的主要内容,如果未能解决你的问题,请参考以下文章

李连杰的二级指针,二级指针的用途,多级指针的定义使用,数组与指针的区别,void 类型指针,引用

用“void*”转换“char”指针并取消引用它

为啥取消引用称为取消引用的指针?

空类型指针(void *)的理解

当一个函数无返回值时,函数的类型应定义为啥

C语言的数据类型——指针