嵌入式开发中 void 的用法总结
- 嵌入式开发
- 1天前
- 32热度
- 0评论
嵌入式开发中 void 的用法总结
在 C 语言里,void 是一个特殊的关键字——它代表"无"或"任意"。对于嵌入式开发者来说,真正需要掌握的核心是 void*(万能指针),它是 C 语言实现"泛型"和"多态"的唯一手段,在 RTOS、驱动抽象、硬件寄存器操作中无处不在。
本文将从基础到实战,系统总结 void 在嵌入式开发中的所有用法。
一、void 作为函数返回值 / 参数
最基础的用法:表示"没有"。
void led_on(void); // 无参数、无返回值
void作返回值 → 函数不返回任何东西void作参数 → 函数明确不接受任何参数(现代 C 标准推荐写法)
注意:
void func()和void func(void)在 C 中含义不同。前者表示"参数未指定",后者才是"明确无参数"。嵌入式开发中推荐始终使用void func(void)。
二、void* 万能指针 — 最核心的用法
int * → 只能指向 int
uint8_t * → 只能指向字节
void * → 什么都能指(使用时需要强制转换)
void* = 万能地址,只保存内存地址,不关心指向的数据类型,使用时再强制转换为具体类型。
核心作用:能指向任何数据类型,常见于以下场景:
- 内存分配返回(
malloc/ 自定义内存池) - 通用回调的"用户数据"参数
- 通用链表/队列/容器(data 字段用
void*) - 寄存器/硬件抽象层传递缓冲区
经典例子一:内存分配(malloc 返回 void*)
malloc 是 void* 最经典的使用场景——它不知道你要分配什么类型的内存,所以返回 void*,由你自己强转:
// malloc 原型:
void *malloc(size_t size); // 返回 void*,你要什么类型自己转
// 标准用法
int *arr = (int *)malloc(10 * sizeof(int)); // 分配 10 个 int
char *buf = (char *)malloc(128); // 分配 128 字节缓冲区
在嵌入式中,很多 RTOS 提供自己的内存分配函数,原理一样:
// FreeRTOS 的内存分配,同样返回 void*
void *pvPortMalloc(size_t xWantedSize);
// 自定义内存池(嵌入式常见做法,避免碎片)
static uint8_t pool[4096]; // 预分配的内存池
static size_t pool_offset = 0;
void *my_malloc(size_t size) {
void *ptr = &pool[pool_offset]; // 返回 void*
pool_offset += size;
return ptr;
}
// 使用时强转为具体类型
typedef struct { uint8_t id; uint16_t value; } SensorData;
SensorData *data = (SensorData *)my_malloc(sizeof(SensorData));
data->id = 1;
data->value = 1024;
核心思路: 内存分配函数只管"给你一块内存",不关心你拿来存什么,所以用 void* 返回。
经典例子二:回调 + void* 传参
同一个 commonPrint 框架,通过传入不同的函数指针和参数,实现打印不同类型的数据:
#include <stdio.h>
// void* 万能指针
void printIntger(void *a)
{
int *pointer, value;
pointer = (int *)a; // 强制转换为 int* 指针
value = *pointer;
printf("打印整型值:%d\r\n", value);
}
// 再写一个回调函数,打印 float
void printFloat(void *a)
{
float *p = (float *)a;
printf("打印浮点值:%f\r\n", *p);
}
// 函数指针 callback(同时传入函数指针 + 函数的形参)
// func: 回调函数指针
// arg: 回调函数需要的参数,用 void* 万能指针传递
void commonPrint(void (*func)(void *), void *arg)
{
func(arg); // 把外部传入的参数转发给回调函数
}
int main(void)
{
int intger = 100;
float fval = 3.14f;
// 直接调用
printIntger(&intger);
// 通过 commonPrint 传入函数指针 + 参数
// 同一个 commonPrint,传不同的回调和参数
commonPrint(printIntger, &intger); // 打印 int
commonPrint(printFloat, &fval); // 打印 float
}
核心套路:函数指针决定"做什么",void* 决定"用什么数据做"。 commonPrint 本身不需要知道数据是什么类型,全靠调用者决定。

经典例子三:pthread_create
// 原型:
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *), // 线程入口函数
void *arg); // 传给线程的参数
用法:
typedef struct {
int id;
char name[32];
} Task;
void *worker(void *arg) {
Task *task = (Task *)arg; // 强转回具体类型
printf("任务 %d: %s\n", task->id, task->name);
return NULL;
}
Task t = {1, "LED闪烁"};
pthread_create(&tid, NULL, worker, &t);
// ↑回调 ↑参数(void*传任意数据)
套路是一样的: 框架(pthread)不知道你要传什么数据,所以用 void* 接收,你在回调里自己强转回来。
三、嵌入式实战场景
场景一:硬件寄存器操作(void* 当内存地址用)
// 直接读写寄存器地址
#define GPIO_BASE ((void *)0x40020000)
volatile uint32_t *gpio = (volatile uint32_t *)GPIO_BASE;
*gpio |= (1 << 5); // 置位第5个引脚
场景二:通用消息队列(RTOS 常用)
// FreeRTOS 的队列可以传任意类型的数据
// 内部就是用 void* + memcpy 按字节搬运
typedef struct {
uint8_t cmd;
uint16_t value;
} Msg;
Msg msg = {0x01, 1024};
// BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
xQueueSend(queue, &msg, portMAX_DELAY); // 传入 void*
场景三:统一的驱动接口(回调 + void*)
// 定义一个通用的传感器驱动接口
typedef struct {
void (*init)(void *config); // 初始化
int (*read)(void *config); // 读数据
void (*deinit)(void *config); // 反初始化
} SensorDriver;
// 温度传感器的实现
typedef struct { uint8_t i2c_addr; } TempConfig;
void temp_init(void *config) {
TempConfig *cfg = (TempConfig *)config;
i2c_init(cfg->i2c_addr);
}
int temp_read(void *config) {
TempConfig *cfg = (TempConfig *)config;
return i2c_read(cfg->i2c_addr);
}
// 注册驱动
SensorDriver temp_drv = { temp_init, temp_read, NULL };
TempConfig temp_cfg = { 0x48 };
// 统一调用,不管是什么传感器
temp_drv.init(&temp_cfg);
int val = temp_drv.read(&temp_cfg);
这就是经典的策略模式——函数指针决定"做什么",void* 决定"用什么数据做"。
四、总结速查表
| 用法 | 含义 | 嵌入式典型场景 |
|---|---|---|
void 无返回值 |
函数不返回数据 | 中断处理函数 void EXTI0_IRQHandler(void) |
void 无参数 |
函数不接受参数 | void system_init(void) |
void* 万能指针 |
可指向任意类型 | malloc、pvPortMalloc、自定义内存池 |
void* + 回调 |
通用接口传递数据 | RTOS 线程、驱动抽象、事件分发 |
void* + 强转 |
运行时类型还原 | 同一套接口处理不同类型数据 |
一句话总结:void* 是 C 语言实现"泛型"和"多态"的唯一手段,嵌入式开发里到处都是。掌握 void* + 函数指针的组合,就掌握了 C 语言面向对象编程的核心。