C void* 通用指针
更新时间:2026-08-26。本文是
languages/c/主题入门层第 41 篇。void*是"不知道类型的指针"——它不指向任何具体类型,也就不能解引用、不能算术。但正因如此,它能当"任意类型的通用搬运工",是 C 泛型能力的基石。
本文要回答的问题
void*能存任何类型的地址吗?反过来呢?- 为什么
malloc返回void*? void*有哪些事不能做?
一、void*:任何指针都能转给它
c
int x = 42;
double d = 3.14;
char s[] = "hi";
void *p;
p = &x; // OK:int* 存进 void*
p = &d; // OK:double* 也行
p = s; // OK:char* 也行void* 是"万能地址容器",任何对象指针都能隐式转成它。反过来(void* 转回具体类型)必须显式转换:
c
void *p = &x;
int *ip = p; // C 里允许(隐式),C++ 不允许
int *ip2 = (int *)p; // 显式转换,最保险的写法二、为什么 malloc 返回 void*
malloc 不知道自己会被用来装什么——所以返回 void*,由调用者决定转成什么:
c
#include <stdlib.h>
int *arr = malloc(10 * sizeof(int)); // void* → int*
double *buf = malloc(100 * sizeof(double));标准库的通用函数都用 void* 当"任意数据":
| 函数 | void* 的作用 |
|---|---|
malloc(size) | 返回任意类型的空间 |
qsort(base, n, size, cmp) | 任意类型数组排序 |
memcpy(dst, src, n) | 任意内存拷贝 |
bsearch(key, base, ...) | 任意类型查找 |
三、void* 不能做什么
c
void *p = ...;
*p; // 错误!不知道类型,解引用无意义
p + 1; // 错误!不知道步长,无法算术| 操作 | void* 能做吗 | 为什么 |
|---|---|---|
| 存地址 / 转来转去 | 能 | 本质是地址 |
解引用 *p | 不能 | 不知道读几个字节 |
算术 p+1 | 不能 | 不知道步长 |
-> 访问成员 | 不能 | 不知道是什么结构 |
要操作必须先转回具体类型:
c
void *p = malloc(sizeof(int));
*(int *)p = 42; // 转回 int* 再解引用四、void* 的"半泛型"能力
C 没有模板,void* 是唯一的泛型手段。以"打印任意类型"为思考题:
c
void print_any(void *p, char kind) {
if (kind == 'i') printf("%d\n", *(int *)p);
else if (kind == 'f') printf("%f\n", *(double *)p);
else printf("未知类型\n");
}
int main(void) {
int x = 7;
double d = 2.5;
print_any(&x, 'i');
print_any(&d, 'f');
return 0;
}代价:类型信息丢失,需要"类型标签"(上面代码里的 kind)来记住怎么解释。这也是 C 里"手写泛型"的基本模式(tagged union 思路),完整方案见高手层通用数据结构的实现。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 解引用 void* | 编译错误 | 先转具体类型 |
| void* 算术 | 编译错误 | 转 char* 再算 |
| 类型标签写错 | 解释错乱 | 标签和实际一致 |
| 忘了强转 | C++ 编译失败 | 显式 (int *) |
| 存"值"而非"地址" | 混乱 | void* 只存地址 |
六、与本站主线衔接
- 通用容器(链表/哈希表的 void* 实现),见高手层通用数据结构;
- 内存操作函数(memcpy/memset)的 void* 家族,见内存与字节操作;
- 无类型指针在 ABI/系统调用里的角色,见专家层内存布局与 ABI。
一句话总结
void* = 无类型地址容器,任何指针可转入、必须显式转出;不能解引用(不知类型)、不能算术(不知步长);malloc 返回它、qsort 靠它实现通用——配合"类型标签"就能在 C 里手搓泛型,这是 C 抽象能力的底线也是上限。