9. void*指针:通用指针类型、类型转换、注意事项

说到 void* 指针,我刚开始学 C 语言的时候,觉得这东西挺玄乎的。什么叫「无类型指针」?指针不就是指向某个类型的内存地址吗?怎么还有「无类型」这种说法?

后来写了几年代码,才真正理解它的价值。说白了,void* 就是 C 语言里最通用的「容器指针」。它不关心你指向的是什么类型,只管记住那个地址。

9.1 什么是 void* 指针

void* 是一种特殊的指针类型。它可以指向任何类型的数据,但本身不包含类型信息。

int a = 42;
float b = 3.14f;
char c = 'X';

void* p1 = &a;  // 指向 int
void* p2 = &b;  // 指向 float
void* p3 = &c;  // 指向 char

你看,同一个 void* 变量,可以指向不同类型的数据。这在 C 语言里是合法的,其他类型的指针可没这个特权。

核心理解:void* 只负责「记住地址」,不负责「解释内容」。解释内容的工作,需要你手动完成。

9.2 为什么需要 void*

我个人觉得,void* 最大的价值在于「通用性」。想想看,如果没有 void*,我们写一个通用的内存分配函数会是什么样子?

// 没有 void* 的尴尬
int*    malloc_int(size_t size);
float*  malloc_float(size_t size);
char*   malloc_char(size_t size);
// ... 每种类型都要写一个版本

这显然不现实。有了 void*,一个 malloc 就搞定了:

void* malloc(size_t size);  // 返回通用指针

我在项目中遇到过类似场景。当时要写一个通用的链表库,节点里存的数据类型不确定。用 void* 来存数据指针,完美解决了这个问题。你想想看,如果没有 void*,链表库得为每种数据类型写一套代码,那得多痛苦。

9.3 类型转换:从 void* 到具体类型

void* 不能直接解引用。为什么呢?因为编译器不知道它指向的是什么类型,也就不知道要读取多少个字节。

void* p = malloc(sizeof(int));
*p = 42;  // 错误!不能解引用 void*

正确的做法是:先转换成具体类型,再解引用。

void* p = malloc(sizeof(int));
int* ip = (int*)p;  // 显式转换
*ip = 42;           // 现在可以了

我的习惯:在 C 语言中,void* 可以隐式转换为其他指针类型,不需要显式强制转换。但为了代码可读性,我建议还是加上显式转换。这样别人一看就知道你在做什么。

// 隐式转换(C 允许)
int* ip = malloc(sizeof(int));

// 显式转换(我推荐)
int* ip = (int*)malloc(sizeof(int));

9.4 注意事项:避坑指南

void* 虽然好用,但用不好就是灾难。我总结了几条经验:

我曾经踩过的坑:

  • 不要对 void* 做指针运算——void* 没有大小概念,p++ 不知道跳多少字节
  • 转换类型要匹配——存进去是 int*,取出来也要转成 int*
  • 注意对齐问题——某些架构对对齐有严格要求
void* p = malloc(10 * sizeof(int));

// 错误:void* 指针运算
// p++;  // 编译器不知道跳多少字节

// 正确:先转换再运算
int* ip = (int*)p;
ip++;  // 跳 4 个字节(假设 int 占 4 字节)

还有一个容易忽略的点:void* 和函数指针不能互相转换。这是 C 标准明确规定的。

void (*func)(void) = my_function;
void* p = (void*)func;  // 未定义行为!

9.5 实际应用场景

void* 最常见的应用场景就是动态内存管理。malloc、calloc、realloc 都返回 void*。

函数 返回值 说明
malloc void* 分配指定字节数的内存
calloc void* 分配并初始化为 0
realloc void* 调整已分配内存的大小

另外,回调函数也经常用到 void*。比如 qsort 的比较函数:

int compare(const void* a, const void* b) {
    int ia = *(const int*)a;
    int ib = *(const int*)b;
    return (ia > ib) - (ia < ib);
}

int arr[] = {3, 1, 4, 1, 5, 9};
qsort(arr, 6, sizeof(int), compare);

你看,qsort 不知道你要排序什么类型,所以用 void* 来接收任意类型的指针。你在比较函数里再转换成具体类型。

9.6 知识体系总览

下面这张图帮你理清 void* 的核心逻辑:

void* 指针 通用指针类型 类型转换 注意事项 可指向任意类型 不包含类型信息 不能直接解引用 显式转换到具体类型 转换后解引用 类型必须匹配 禁止指针运算 注意内存对齐 不与函数指针互转

嗯,这张图把 void* 的核心知识点都串起来了。你写代码的时候,可以对照着看看自己有没有踩到坑。

一句话总结:void* 是 C 语言里最灵活的指针,但灵活意味着责任。用之前想清楚:我要存什么类型?取出来要转成什么?有没有对齐问题?想清楚了再动手。


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