38、void 指针:void* 有什么用?如何解引用?

说实话,void 指针是 C 语言里一个挺「特别」的存在。很多初学者看到它就觉得头大——「void 不是空的意思吗?那 void* 到底是个啥?」

嗯,我当年学到这里也困惑过。后来在嵌入式项目里被它「救」过几次,才真正理解了它的价值。今天咱们就把它彻底讲透。

一、void* 到底是什么?

简单说,void* 是一个「无类型指针」。它不指向任何特定类型的数据,而是可以指向任意类型的数据。

你想想看,int* 只能指向 int 数据,char* 只能指向 char 数据。但 void* 不一样——它像个「万能插座」,什么类型的地址都能存。

核心定义:void* 是一个指针,它存储的是内存地址,但编译器不知道这个地址里存的是什么类型的数据。

二、void* 到底有什么用?

我在项目中遇到过好几次这样的场景:写一个通用的函数,要处理不同类型的数据。这时候 void* 就派上大用场了。

具体来说,它的主要用途有这几个:

  1. 实现通用函数——比如标准库里的 memcpy、memset、qsort,都用 void* 来接受任意类型的参数
  2. 封装不透明数据类型——在嵌入式驱动开发中,经常用 void* 隐藏内部结构体的细节
  3. 回调函数的参数传递——把任意类型的数据传给回调函数
  4. 内存管理——malloc 返回的就是 void*,因为它不知道你要存什么类型

说白了,void* 就是 C 语言实现「泛型编程」的一种手段。虽然不像 C++ 的模板那么优雅,但在 C 的世界里,它是最实用的方案。

三、void* 如何解引用?

这里有个关键点:void* 不能直接解引用

为什么?因为解引用操作需要知道指针指向的数据类型,才能确定读取几个字节、怎么解释这些字节。void* 没有类型信息,编译器不知道该怎么处理。

来看个错误示例:

int a = 100;
void* p = &a;
printf("%d\n", *p);  // ❌ 编译错误:不能解引用 void*

那怎么办?必须先强制类型转换,告诉编译器「这个地址里存的是某某类型的数据」。

int a = 100;
void* p = &a;
int* q = (int*)p;    // 强制转换为 int*
printf("%d\n", *q);  // ✅ 输出:100

注意:强制类型转换时,要确保转换后的类型和实际存储的数据类型一致。否则解引用时会得到错误的值,甚至引发未定义行为。

四、void* 的指针运算

这个问题我踩过坑。标准 C 规定:void* 不能进行算术运算

比如 p++、p+1 这种操作,对 void* 来说是违法的。因为指针加 1 意味着跳过「一个元素」的大小,但 void* 不知道一个元素有多大。

void* p = malloc(100);
p++;  // ❌ 编译错误:void* 不能进行算术运算

不过,有些编译器(比如 GCC)把 void* 当作 char* 来处理,允许算术运算。但这是编译器扩展,不是标准行为。我建议你不要依赖这个特性,否则换一个编译器代码就挂了。

正确的做法是:先转换成 char* 或具体类型的指针,再做运算。

void* p = malloc(100);
char* cp = (char*)p;
cp++;  // ✅ 现在可以了

五、避坑指南

我曾经在一个通信协议栈的项目里,因为 void* 用错了,排查了整整两天。这里分享几个我总结的经验:

  • 不要对 void* 直接解引用——必须先转换类型
  • 不要对 void* 做算术运算——先转成具体类型再操作
  • 转换类型时要小心对齐问题——特别是在嵌入式系统里,某些架构对对齐有严格要求
  • void* 和函数指针不能互相转换——标准 C 不保证这种行为

个人习惯:我在写通用函数时,会用 void* 接收参数,但内部会立即转换成实际类型。这样既保证了接口的通用性,又避免了后续操作中的类型混乱。

六、知识体系图

下面这张图帮你理清 void* 的核心知识点:

void* 指针 无类型指针,可指向任意类型 主要用途 通用函数参数 封装数据类型 回调参数传递 内存管理 解引用:必须先强制类型转换 (int*)p → 再解引用 *((int*)p) 禁止算术运算(p++、p+1 等)

七、总结

void* 是 C 语言里一个非常实用的工具。它让函数可以处理任意类型的数据,是实现通用代码的基础。但使用它需要遵守两条铁律:

  • 解引用前必须强制类型转换
  • 不要做指针算术运算

记住这两点,void* 就是你手中的利器。用不好,它就是调试时的噩梦。

我个人觉得,void* 的设计体现了 C 语言的一种哲学:给你足够的灵活性,同时也要求你对自己的代码负责。嗯,这很 C。


公众号:蓝海资料掘金营,微信 deep3321