C语言 通用型指针 void* 详解 + 小习题

什么是通用型指针(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.2

int a = 10, b = 20;

void* p1 = &a;

void* p2 = &b;

if (p1 < p2)

{

......; //通用型指针可以比较大小

}
NO.3

int 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语言的严格别名规则禁止通过不同类型的指针访问同一内存位置

虽然可能不会立即崩溃,但会导致

  • 得到无意义的值
  • 后续计算错误
  • 在某些架构上产生硬件异常