void*是怎样的存在?

论坛 期权论坛 金融     
xjy1h   2022-6-4 22:23   13776   20
说到C就不得不提指针,而一提到指针,有一个是比较特殊的,那就是void*。void*到底是怎样的存在?
指针类型的含义

在说明void*之前,先了解一下普通指针类型的含义。
//来源:公众号【编程珠玑】
//main.c
#include <stdio.h>
int main(void)
{
    int a[] = {0x01020304,2019};
    int *b = a;
    char *c = (char*)&a[0];
    printf("b+1:%d\n",*(b+1));
    printf("c+1:%d\n",*(c+1));
    return 0;
}
`
上面的输出结果为:
b+1:2019
c+1:3
对于上面的结果,也许你并不感到意外。如果你的疑问是为什么不是2而是3,那么建议你看看《谈一谈字节序的问题》。同样是指针类型,b和c有什么区别?
一个是指向整型的指针,一个是指向char型的指针,当它们执行算术运算时,它们的步长就是对应类型占用空间大小。

b + 1 //移动sizeof(int)字节040302012019字节0字节1字节2字节3字节4~7

↑指针移动4个字节后,指向的就是2019了,解引用自然得到2019。而对于c
c + 1 //移动sizeof(char)字节
它的指向如下:040302012019字节0字节1字节2字节3字节4~7


解引用之后,自然得到3。
结论

各种类型之间没有本质区别,只是解释内存中的数据方式不同。例如,对于int型指针b,解引用时,会解析4字节,算术运算时,也是以该类型占用空间大小为单位,所以b+1,移动4字节,解引用,处理4字节内容,得到2019。对于char型指针c,解引用时,会解析1个字节,算术运算时,也是以sizeof(char)为单位,所以c+1,移动一字节,解引用,处理1字节,得到03。所以像下面这样的操作:
char a[] = {01,02,03,04};
int *b = (int*)(a+2);
如果你试图解引用b,即*b,就可能遇到无法预料的问题,因为将会访问非法内存位置。a+2,移动sizeof(char)字节,指向03,此时按照int类型指针解引用,由于int类型解引用会处理4字节内存,但是后面已经没有属于数组a的合法内容了,因此可能出错。
指针占用空间大小

正由于它们没有本质区别,它们占用空间大小在同一个程序中都是固定的,对于32位程序,占用4字节空间,64位占用8字节,而正因如此,64位程序理论能使用的内存是足够大的,而32位程序理论上能使用的不过4G(2^(4*8bit)),再加上内核空间的使用,真正能用到的可能就3G左右。如果你的系统是64位的,那么默认情况下,编译出来的程序也是64位的。如果你想编译为32位,可以使用-m32参数:
$ gcc -m32 -o main main.c
如何确定是多少位的程序:
$ readelf -h main
Class:                             ELF32
上面的ELF32,表明了它是32位程序。或者可以看Machine字段:
Machine:                           Intel 80386
void*

说回void*,前面说了,指针的类型不过是解释数据的方式不同罢了,这样的道理也可用于很多场合的强制类型转换,例如将int类型指针转换为char型指针,并不会改变内存的实际内容,只是修改了解释方式而已。而void *是一种无类型指针,任何类型指针都可以转为void\*,它无条件接受各种类型。而既然是无类型指针,那么就不要尝试做下面的事情:

  • 解引用
  • 算术运算
由于不知道其解引用操作的内存大小,以及算术运算操作的大小,因此它的结果是未知的。
#include <stdio.h>
int main(void)
{
    int a = 10;
    int *b = &a;
    void *c = b;
    *c;
    return 0;
}
编译警告如下:
warning: dereferencing ‘void *’ pointer
如何使用

既然如此,那么void*有什么用呢?实际上我们在很多接口中都会发现它们的参数类型都是void*,例如:
ssize_t read(int fd, void *buf, size_t count);
void *memcpy(void *dest, const void *src, size_t n);
为何要如此设计?因为对于这种通用型接口,你不知道用户的数据类型是什么,但是你必须能够处理用户的各种类型数据,因而会使用void*。void*能包容地接受各种类型的指针。也就是说,如果你期望接口能够接受任何类型的参数,你可以使用void*类型。但是在具体使用的时候,你必须转换为具体的指针类型。例如,你传入接口的是int*,那么你在使用的时候就应该按照int*使用。
注意

使用void*需要特别注意的是,你必须清楚原始传入的是什么类型,然后转换成对应类型。例如,你准备使用库函数qsort进行排序:
void qsort(void *base,size_t nmemb,size_t size , int(*compar)(const void *,const void *));
它的第三个参数就是比较函数,它接受的参数都是const void*,如果你的比较对象是一个结构体类型,那么你自己在实现compar函数的时候,也必须是转换为该结构体类型使用。举个例子,你要实现学生信息按照成绩比较:
//来源:公众号【编程珠玑】
typedef struct student_tag
{
    char name[STU_NAME_LEN];  //学生姓名
    unsigned int id;          //学生学号
    int score;                //学生成绩
}student_t;
int studentCompare(const void *stu1,const void *stu2)
{
  /*强转成需要比较的数据结构*/
    student_t *value1 = (student_t*)stu1;
    student_t *value2 = (student_t*)stu2;
    return value1->score-value2->score;
}
在将其传入studentCompare函数后,必须转换为其对应的类型进行处理。
更多函数指针相关内容可以参考《高级指针话题-函数指针》,那里有更多的介绍。
总结

void*很强大,但是一定要在合适的时候使用;同时强转很逆天,但是一定要注意前后的类型是否真的能正确转换。通俗地说void*:

  • 这里有一片内存数据,我也不知道什么类型,给你了,你自己想怎么用怎么用吧,不过要用对奥!
  • 我这里什么类型都能处理,你给我一片内存数据就可以了
原文链接:void*到底是什么玩意?
高级指针话题-函数指针

谈一谈字节序的问题

为何优先选用unique_ptr而不是裸指针?

函数参数的传值和传指针有什么区别?


关注公众号【编程珠玑】,获取更多Linux/C/C++/数据结构与算法/计算机基础/工具等原创技术文章。后台免费获取经典电子书和视频资源
分享到 :
0 人收藏

20 个回复

倒序浏览
2#
lp8b0  1级新秀 | 2022-6-4 22:24:48 发帖IP地址来自 北京
几句话说完:
指针是对内存区域的抽象。指针变量中存放着目标对象的内存地址,而与指针相复合的类型,则说明了相应内存区域中的内容具有哪些属性,以及能做什么事情。也就是说,在内存空间某块区域中的内容,原本可以是不可解读的;但是,如果有一个描述这块内存区域的指针存在,我们就能找到它(地址的作用),并且合理地使用它(类型的作用)。void* 只有其中一半的作用。因为没有明确与指针相复合的类型,所以不能解引用,也不能使用基于类型之上(sizeof(T))的指针运算。
3#
二元期权  1级新秀 | 2022-6-4 22:25:30 发帖IP地址来自 中国
4#
pdn1a  1级新秀 | 2022-6-4 22:26:11 发帖IP地址来自 中国
void* 是啥? 嗨,object。
5#
ifl7  1级新秀 | 2022-6-4 22:26:35 发帖IP地址来自 中国
说白了都是给编译器用的   给人看的   机器管你void还是 int
6#
sg0511  2级吧友 | 2022-6-4 22:27:26 发帖IP地址来自 中国
提示: 作者被禁止或删除 内容自动屏蔽
7#
无情1  1级新秀 | 2022-6-4 22:27:40 发帖IP地址来自 北京
没有说一定是3,但是你的x86会是
8#
qq7117090  1级新秀 | 2022-6-4 22:28:21 发帖IP地址来自 北京
不就是any*,c不肯加关键字而已了
9#
mnsm  1级新秀 | 2022-6-4 22:28:31 发帖IP地址来自 北京
还是说清楚大小端问题吧,会误导小白
10#
babanet  1级新秀 | 2022-6-4 22:28:52 发帖IP地址来自 广东
在另一篇中有说明
11#
m0rl  1级新秀 | 2022-6-4 22:29:07 发帖IP地址来自 北京朝阳
所以最好能说更清楚些,以免用树莓派或者Android NDK的人来杠……
12#
raminsclose  1级新秀 | 2022-6-4 22:29:12 发帖IP地址来自 中国
大小端没考虑吧
13#
xiaxiatmd  1级新秀 | 2022-6-4 22:29:25 发帖IP地址来自 北京
再仔细看看
14#
198qjn  1级新秀 | 2022-6-4 22:29:56 发帖IP地址来自 中国
可以自己定义
15#
4jw9u  1级新秀 | 2022-6-4 22:30:51 发帖IP地址来自 北京
typedef void* Object;
16#
zdh58888  1级新秀 | 2022-6-4 22:31:02 发帖IP地址来自 中国
怪不得C++有函数重载和模板
17#
dl_v7  1级新秀 | 2022-6-4 22:31:42 发帖IP地址来自 北京
void*   是什么,是世界的真相     本质上只有void*      int*是编译器魔法
18#
fqingh  1级新秀 | 2022-6-4 22:32:38 发帖IP地址来自 云南
你是说的大端模式和小端模式吧?
19#
hjt  1级新秀 | 2022-6-4 22:33:24 发帖IP地址来自 北京
感谢分享,还有一个问题请教一下,void **是一个什么样的存在呢?在pthread_join参数中为什么要写成这种指针的指针的形式?
20#
云上のAzkab  1级新秀 | 2022-6-4 22:33:42 发帖IP地址来自 北京
如果你想改变指针变量本身值而不是而不是指针指向的内容,你会怎么做
21#
yymb  1级新秀 | 2022-6-4 22:34:22 发帖IP地址来自 吉林长春
64位的void*是不是LONG_PTR啊?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:
帖子:
精华:
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP