网站首页 > 技术文章 正文
0. 从void说起
void*是C的产物,要说清楚void*我们先从void说起。
void即为不确定类型--类型不确定,从而所占内存不确定。因为C是强类型语言,所以void par = 10;之类的声明是万万不可的,即void类型不能声明实例对象。在C语言中,void的作用主要有以下两种:
- 对函数返回类型的限定,利用void对象的大小不确定来限制函数不能有任何返回值。-- 这就是我们常写的void作返回值的函数。
- 对函数参数类型的限定,当函数不允许接受参数时,必须用void来限定函数的参数:int func(void)。-- 当然现在没什么人会这么写了,直接写int func()即可。
1. void*是什么
不同于void,编译器允许你做类似于int par = 10; void* ppar = ∥之类的操作,因为无论指针指向什么类型,指针本身所占空间是一定的。我们可以认为void*就是一个通用指针,可以指向任意类型对象,因此void*常被称为“万能指针”。
我们都知道,指针有两个属性:所指对象的地址、长度。但是指针只存储被指向对象的地址,长度则取决于指针的类型,编译器根据指针的类型从指针指向的地址向后寻址,不同的类型则寻址范围不同,各种类型之间没有本质区别,只是解释内存中的数据方式不同。对于int*,解引用时,会从指定地址向后寻找4个字节作为变量的存储单元;对于char*,会解析1个字节。而我们将一个void类型的指针指向一个int类型的实例,实际上是抹去了这一实例的类型信息,因此在使用时我们要在心里清楚被抹去的类型信息。因此,void*也可以被称为“无类型指针”,任何类型指针都可以转为void*,它无条件接受各种类型指针。
2. 如何使用void*
既然如此,那么void*有什么用呢?其实,我们在很多接口中都能看到void*,例如
void* memcpy(void* dest, const void* src, sizt_t len);
void *memset(void *buffer, int c, int count);
任何类型的指针都可以传入memcpy和memset中,这也真正体现了内存操作函数的意义。void*帮我们屏蔽了冗余的变量类型信息,而直接将内存暴露在我们面前。
又如
ssize_t read(int fd, void* buf, size_t count);
那么,为何要如此设计?因为对于这种通用型接口,你不知道用户的数据类型是什么,但是你必须能够处理用户的各种类型数据,因而会使用void*。void*能包容的接受各种类型的指针。也就是说,如果你期望接口能接受任何类型的参数,你可以使用void*类型。但是在具体使用的时候,你必须转换为具体的指针类型。例如,你传入接口的是int*,那么你在使用的时候就应该按照int*使用。
3. void*的约束
3.1 void*可以指向任何类型的地址,但带类型的指针不能指向void*的地址
正常来说如果两个指针类型不一样的话,两个指针变量是不可以直接赋值的。如:
int* a, float* b;
a = b; //编译错误
而任何类型的指针可以赋值给void*指针,但是反过来不可以,也就是说一个有类型的指针不能指向一个void类型的变量(哪怕此时void*变量)已经指向了一个有类型的地址。
int a = 5;
int* pa = &a;
void* pb = pa;
int* pa2 = pb; //编译错误,有类型的指针变量不能指向void*变量
3.2 void*指针只有强制类型转换以后才能正常取值
既然是无类型指针,那么就不要尝试做下面的事情:
- 解引用
- 算数运算
由于不知道其解引用操作的内存大小,以及算数运算操作的大小,因此它的结果是未知的。
#include <stdio.h>
int main(void) {
int a = 10;
int *b = &a;
void *c = b;
*c; //*(int*)c就对了
return 0;
}
编译器警告如下:
warning: dereferencing 'void*' pointer
所以,void*指针只有强制类型转换以后才可以正常取值与运算。
4. void*与template
对于c++而言,对于某些泛型可以实现的功能但又想找一种轻量化的实现方法,void*也不失为一种不错的选择。
毫无疑问,在可能的情况下使用void*会提高编译速度,但这并不应该成为我们大量使用void*的理由,正如前面所看到的,void*会将很多隐藏的问题埋入深坑,相比template而言,编译期就能暴露的问题被推迟到运行时,隐含bug风险显著提升。
5. 总结
void*很强大,但是一定要在合适的时候使用;同时强转很逆天,一定要注意前后的类型是否真的能正确转换。
通俗的讲,void*:
- 这里有一片内存数据,我也不知道什么类型,给你了,你自己想怎么用怎么用吧,不过要用对奥。
- 我这里什么类型都能处理,你给我一片内存数据就可以了。
猜你喜欢
- 2024-10-26 C语言、嵌入式项目中一些常用知识及技巧第一弹
- 2024-10-26 C语言编程:最常见 7 道C语言面试题,还是有不少人弄不明白?
- 2024-10-26 初识C语言:简介、环境搭建、第一个HelloWorld
- 2024-10-26 C|volatile关键字使用细节及适用场合
- 2024-10-26 C语言干货:函数知识详解(变量的作用域,全局变量,静态变量)
- 2024-10-26 小白入门C语言20问20答2(新手c语言)
- 2024-10-26 C语言的简单了解及学习2(c语言的入门知识)
- 2024-10-26 C 语言基本语法(c语言的基础语法)
- 2024-10-26 C语言数据怎么描述?最全面解析,C语言基础教学档案!编号零零五
- 2024-10-26 C语言void关键字的高级玩法,6个样例代码告诉你
- 1507℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 505℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 484℃MySQL service启动脚本浅析(r12笔记第59天)
- 465℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 462℃启用MySQL查询缓存(mysql8.0查询缓存)
- 442℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 422℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 418℃MySQL server PID file could not be found!失败
- 最近发表
-
- netty系列之:搭建HTTP上传文件服务器
- 让deepseek教我将deepseek接入word
- 前端大文件分片上传断点续传(前端大文件分片上传断点续传怎么操作)
- POST 为什么会发送两次请求?(post+为什么会发送两次请求?怎么回答)
- Jmeter之HTTP请求与响应(jmeter运行http请求没反应)
- WAF-Bypass之SQL注入绕过思路总结
- 用户疯狂点击上传按钮,如何确保只有一个上传任务在执行?
- 二 计算机网络 前端学习 物理层 链路层 网络层 传输层 应用层 HTTP
- HTTP请求的完全过程(http请求的基本过程)
- dart系列之:浏览器中的舞者,用dart发送HTTP请求
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)