什么是通用型指针(void*)?
通用型指针 void* 是C语言中一种特殊的指针类型,它可以指向任何类型的数据,但不知道所指向数据的类型。
核心特性:
特 性 说 明
- 通用性 可以存储任何类型数据的地址
- 无类型 不知道指向的数据是什么类型
- 不能直接解引用 必须先转换为具体类型才能访问数据
- 不能直接算术运算 void*++、void*+1 等操作不允许
- 可以比较地址 void* 可以比较大小(地址比较)
- 可以强制转换 需要时转换为具体指针类型
sample 01 :
int main(void) {
int someInt;
int* valPointer;
someInt = 5;
printf("someInt address is %p \n", (void*) &someInt );
// 明确告诉编译器:"我知道这是int*,但我特意要当作void*使用"
valPointer = &someInt;
printf("valPointer is %p \n", (void*) valPointer );
printf("*valPointer is %d\n", *valPointer);
*valPointer = 10; // Changes someInt to 10
printf("someInt is %d\n", someInt);
printf("*valPointer is %d\n", *valPointer);
return 0;
}
output :someInt address is 0x7ffe5659d874
valPointer is 0x7ffe5659d874
*valPointer is 5
someInt is 10
*valPointer is 10
为什么要用 (void*) 转换 &someInt
1. 类型安全性
不安全的写法:
printf("地址: %p\n", &someInt);
编译可能通过,但可能产生警告:
warning: format '%p' expects argument of type 'void*'
安全的写法:
printf("someInt address is %p\n", (void*) &someInt);
明确告诉编译器:"我知道这是int*,但我特意要当作void*使用"
2. 可移植性
- 不同的系统可能有不同的指针表示:
- 某些系统上 int* 和 char* 可能有不同的内部表示
- void* 保证了最大的兼容性
- %p 格式说明符专门设计为接收 void* 类型
3. 代码清晰性
- 模糊:这是地址,但不知道是什么的地址
printf("%p\n", &someInt); - 清晰:这是通用地址,我特意转换了
printf("%p\n", (void*)&someInt); - 更清晰的对比:
printf("int地址: %p\n", (void*)&someInt);
printf("函数地址: %p\n", (void*)&main);
printf("字符串地址: %p\n", (void*)"hello");
都统一用(void*),表示"我只是要地址,不关心类型"
4. 符合C标准
C标准明确规定:
The argument shall be a pointer to void.
%p 期望的是 void* 参数,传递其他类型是未定义行为(尽管很多编译器允许)。
6. 与C++的兼容性
在C++中,要求更严格
int x = 10;
std::cout << &x << std::endl; // 自动转换为void*
printf("%p\n", &x); // C++中可能编译错误
printf("%p\n", (void*)&x); // C/C++都安全
小结 :
(void*) 转换是在说:"我知道这是一个具体类型的指针,但我现在要把它当作无类型指针使用"。这种显式转换比隐式转换更安全、更清晰。
几个让你正确使用通用指针的列子:
sample 02 :
typedef struct{
char name[20];
int age;
}Person;
int main()
{
int n = 23;
float x = 45.67;
double y = 23.34;
char str[] = "Notre Dame";
Person prsn = {"Pat",10};
void* p;
p = &n;
printf("%d\n",*(int*) p);
p = &x;
printf("%f\n",*(float*) p);
p = &y;
printf("%lf\n",*(double*) p);
p = str;
printf("%s\n",*(char*) p);
p = &prsn;
printf("%s is %d years old\n",((Person *)p)->name, ((Person *)p)->age);
}
sample 03 :
typedef struct{
char name[20];
int id;
double score;
}Student;
void print_student(void* data)
{
Student* s = (Student*) data;
printf("name: %s id: %d score: %.1f\n",s->name,s->id,s->score)
}
void update_score(void* data,float new_score)
{
((Student*) data)->score = new_score;
}
int main(){
Student stu ={"zhangzhan",1001,85.5};
print_student(&stu);
update_score(&stu,92.0);
print_student(&stu);
return 0;
}
通用型指针使用场景:
场景1:通用函数参数(最常见)
// 通用内存操作函数
void* memcpy(void* dest, const void* src, size_t n);
void* memset(void* dest, int ch, size_t count);
void qsort(void* base, size_t nmemb, size_t size,
int (*compar)(const void*, const void*));
// 通用交换函数
void swap(void* a, void* b, size_t size) {
char temp[size];
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}
通用指针的便利性 :
通用指针让你写出"不知道类型也能干活"的代码:
// 对比没有通用指针 的情况,我们需要为每种类型写一个函数
void swap_int(int* a, int* b) {
int temp = *a; *a = *b; *b = temp;
}
void swap_float(float* a, float* b) {
float temp = *a; *a = *b; *b = temp;
}
void swap_double(double* a, double* b) {
double temp = *a; *a = *b; *b = temp;
}
void swap_student(Student* a, Student* b) {
Student temp = *a; *a = *b; *b = temp;
}
// ... 每种类型都要写一个!
有了通用指针 :一个函数搞定所有// 一个函数处理所有类型
void swap(void* a, void* b, size_t size) {
char temp[size]; // 按字节操作
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}
// 全部能用 !
swap(&x, &y, sizeof(int)); // int
swap(&f1, &f2, sizeof(float)); // float
swap(&stu1, &stu2, sizeof(Student)); // Student
场景2:动态内存分配
// malloc返回void*,可以分配给任何类型
int* int_array = (int*)malloc(10 * sizeof(int));
char* string = (char*)malloc(100 * sizeof(char));
Student* students = (Student*)malloc(5 * sizeof(Student));
如果不使用void* ,每种类型一个malloc函数// 假设C标准库不用void*,会是什么样子?
// 要为每种类型都写一个 分配函数
int* malloc_int(size_t count) {
return (int*)系统内存分配(count * sizeof(int));
}
float* malloc_float(size_t count) {
return (float*)系统内存分配(count * sizeof(float));
}
char* malloc_char(size_t count) {
return (char*)系统内存分配(count * sizeof(char));
}
Student* malloc_student(size_t count) {
return (Student*)系统内存分配(count * sizeof(Student));
}
// 使用:太麻烦了!
int* arr1 = malloc_int(100);
float* arr2 = malloc_float(50);
char* str = malloc_char(1000);
Student* students = malloc_student(30);
// 问题来了:那结构体呢?每定义一个新结构体,就要写一个malloc_xxx函数!
// 现实中的C标准库(用void*)void* malloc(size_t size); // size是要的字节数
// 使用:简单统一 !
int* arr1 = (int*)malloc(100 * sizeof(int));
float* arr2 = (float*)malloc(50 * sizeof(float));
char* str = (char*)malloc(1000 * sizeof(char));
Student* students = (Student*)malloc(30 * sizeof(Student));
// 新结构体?不用写新函数 !
typedef struct { /* ... */ } 新类型;
新类型* 新数组 = (新类型*)malloc(100 * sizeof(新类型));
// 直接就能用!
场景3:回调函数接口
// 使用void* 的一套通用回调系统
typedef void (*Callback)(void* data); // void* 万能参数
void process_data(void* data, Callback callback) {
callback(data); // 什么类型都能传
}
// 不同的回调函数
void handle_int(void* data) {
int* p = (int*)data;
printf("处理整数: %d\n", *p);
}
void handle_student(void* data) {
Student* s = (Student*)data;
printf("处理学生: %s\n", s->name);
}
// 使用:同一套 process_data函数
int x = 100;
Student stu = {"张三", 20};
process_data(&x, handle_int); // 处理整数
process_data(&stu, handle_student); // 处理学生
// 未来还能处理:float、Product、Car... 不用改process_data!
对比不使用通用指针void*// 只能处理int 的回调系统
typedef void (*IntCallback)(int* data);
void process_int(int* data, IntCallback callback) {
callback(data); // 只能传int*
}
// 只能处理Student 的回调系统
typedef void (*StudentCallback)(Student* data);
void process_student(Student* data, StudentCallback callback) {
callback(data); // 只能传Student*
}
// 问题 :每来一种新类型,就要写一套新系统!
场景4:数据结构实现(泛型)
// 泛型链表节点
typedef struct Node {
void* data; // 存储任何类型的数据
struct Node* next;
} Node;
// 泛型容器
typedef struct {
void** items; // void*数组
size_t size;
size_t capacity;
} GenericArray;
场景5:硬件/底层编程
// 直接操作内存地址
void* device_memory = (void*)0x80000000; // 设备内存地址
uint32_t* reg = (uint32_t*)device_memory;
*reg = 0x12345678; // 写入设备寄存器
使用通用型指针的注意事项,以及一些用来辨析通用指针的小练习:
NO.1
int x = 100;
void* p = &x;
p++ //通用型指针不可以进行算数运算
NO.2int a = 10, b = 20;
void* p1 = &a;
void* p2 = &b;
if (p1 < p2)
{
......; //通用型指针可以比较大小
}
NO.3int x = 10;
void* vp = &x;
以下哪句是错误的?
printf("Address: %p\n",vp);
printf("Value : %d\n",*(int*) vp);
printf("Value : %d\n",*vp); //这句是错误的 通用类型指针不能直接解引用
int* ip = (int*) vp;
NO.4判断对错
int n = 10;
void* p = &n;
int value = *p; //错 通用指针不能直接解引用
int value1 = *(int*) p; //对
int* int_ptr = (int*) p; //对
NO.5以下代码输出什么?
int x = 0x12345678;
void* p = &x;
char* cp = (char*)p;
printf("%02X\n", *cp); // 假设是小端序系统
在小端序系统中, int x = 0x1234678;
低地址 -> 高地址
78 56 34 12
↑
首字节
char* cp = (char*)p 指向首字节,*cp 取得 0x78,%02X 输出 78。
NO.6找出错误
float f = 3.14;
void* vp = &f;
printf("%f\n", *vp); //错误,*vp 不能直接解引用,应为 *(float*)vp
NO.7判断以下哪个操作是合法的
int arr[5] = {1,2,3,4,5};
void* p = arr;
A. p++ //void* 指针不能进行算术运算(不知道类型大小)
B. p[2] //p 是 void*,不知道元素大小,无法计算偏移
C. (int*)p + 2 //合法
D. *(p + 2) //合法
E. ((int*)p)[2] //同A,p+2 的算术运算非法(void* 不能加减)
NO.8// 实现一个通用的"查找最大值"函数
// 参数:void*数组,元素个数,元素大小,比较函数
void* find_max(void* array, int count, size_t size, int (*compare)(const void*, const void*));
// 比较函数示例
int compare_int(const void* a, const void* b) {
return *(int*)a - *(int*)b;
}
int compare_string(const void* a, const void* b) {
return strcmp(*(char**)a, *(char**)b);
}
NO.9为什么以下代码可能崩溃?
void process(void* data) {
int value = *(int*)data; // 假设data指向int
// ... 使用value
}
int main() {
float f = 3.14;
process(&f); // 传递了float地址,但函数按int解释
return 0;
}
类型不匹配(Type Mismatch) :process() 函数将传入的 float* 当作 int* 解释
大小差异 :在大多数系统上 int (4字节) 和 float (4字节) 大小相同,但位模式解释不同
数据解释错误 :浮点数 3.14 的 IEEE 754 位模式 0x4048F5C3 被解释为整数时是十进制的 1,078,523,331,可能导致逻辑错误
对齐问题 :在某些架构上,如果 float 和 int 的对齐要求不同,可能访问无效内存地址
严格别名规则违反:C语言的严格别名规则禁止通过不同类型的指针访问同一内存位置虽然可能不会立即崩溃,但会导致:
- 得到无意义的值
- 后续计算错误
- 在某些架构上产生硬件异常