任务1 app_dpd0.c
cpp
static uint32_t workIndex = 0;
rt_thread_create("sleepApp", sleep_manager_thread, &workIndex, 4096, 12, 10);
static void sleep_manager_thread(void *param){
uint32_t *index = (uint32_t *)param;
rt_kprintf("[*index=%d] sleep_manager_thread get sem \n",*index);
}

典型代码如上 传递参数给任务 任务拿着*index使用 表现正常正确
任务2 \app_modbus.c
cpp
static int modbus_app_start(void){
extern uint32_t workIndex_get(void);
uint32_t workIndex = workIndex_get();
rt_thread_t tid = rt_thread_create("busApp",thread_modbus_entry,(void *)workIndex,1024,20,10);
if (tid ) rt_thread_startup(tid);
return 0;
}
static void thread_modbus_entry(void *param) {
uint32_t index = (uint32_t)param;
rt_kprintf("index=%d\n", index);
}

典型代码如上 传递参数给任务 任务拿着index使用 表现正常正确
坑在哪里?
上面两份代码都是成功了 其中第二份代码很容易翻车!

void* 做万能转 核心绘图如上
A模块把参数传递给B 需要AB提前约定统一void*转成啥样的结构体!所以是一个氧化-还原反应 AB心知肚明即可! 而嵌入式尤其喜欢传递地址简简单单地址4个字节就可以拿过去任意转化 非常妙呀!天坑也就在这里 隐含条件是地址上的值是不容更改的!
可以想象一下:
A操作的时候地址0X11223344的值是0XAABBCCDD
此时A把地址0X11223344打包成void*传递给B
而后面B收到 开始把0X11223344按照约定换成值 的时候
物理上地址0X11223344的值不再维持0XAABBCCDD
那么传递就失效了 甚至是灾难!
演示 任务2 \app_modbus.c的 bug写法

Bug 根因:把 workIndex 这个局部变量的地址传给了线程
为什么外面是 4,任务里不是 4?
workIndex是
modbus_app_start的局部变量 ,存放在栈上。
modbus_app_start执行到
return 0返回后,这个栈帧就被回收/覆盖了。线程
busApp是异步 启动的,它真正运行thread_modbus_entry的时间点晚于modbus_app_start返回。等你线程拿到
param(即&workIndex)去解引用时,这块栈内存早被其他函数调用覆盖,读到的自然是垃圾值(不是 4)。
这正是 RT-Thread/FreeRtos嵌入式多线程开发里最常见的坑之一:把局部变量地址传给新线程。
正确做法(三种)



小结
1>址与值 必须要清晰的认知
2>void* 做万能转
2.1需要AB提前约定统一void*转成啥样的结构体
2.2 隐含条件是地址上的值不容更改
FreeRtos在创建任务时如何正确传递参数
FreeRTOS 里 xTaskCreate(..., void *pvParameters, ...) 的第四个参数本质是 一个 void* 值 ,内核只会把这个"指针值/整数值"原样交给任务函数,不会帮你拷贝它指向的数据。所以"怎么传才对"核心就一句话:
如果你传的是"地址",那个地址指向的对象必须活得比任务使用它的时间更长。
1️⃣ 传整数 / 枚举 / 小标识(最安全)
不需要取地址,直接把整数强转成 void*:
cpp
void vLedTask(void *pvParameters)
{
int led_id = (int)pvParameters;
for (;;)
{
// 用 led_id
}
}
xTaskCreate(vLedTask, "LED", 256, (void *)1, 1, NULL);
xTaskCreate(vLedTask, "LED", 256, (void *)2, 1, NULL);
✅ 没有指针、没有生命周期问题
⚠️ 只能传能放进指针的整数(通常没问题)
2️⃣ 传结构体 / 配置参数(最常见坑)
❌ 错误:传局部变量地址
cpp
void CreateTask(void)
{
TaskConfig cfg = { .baud = 115200, .mode = 1 };
xTaskCreate(vTask, "T", 256, &cfg, 1, NULL);
// cfg 在函数返回后就失效
}
任务什么时候跑是不确定的,等它读 cfg 时,栈已经被覆盖 → 野指针 / 乱数据。
✅ 正确做法 1:用 static / 全局变量
cpp
static TaskConfig cfg1 = { .baud = 115200, .mode = 1 };
xTaskCreate(vTask, "T", 256, &cfg1, 1, NULL);
任务里:
cpp
void vTask(void *pv)
{
TaskConfig *cfg = (TaskConfig *)pv;
for (;;) { }
}
✅ 生命周期 = 整个程序
✅ 最适合"每个任务一份固定配置"
✅ 正确做法 2:堆里分配(动态参数)
cpp
TaskConfig *cfg = pvPortMalloc(sizeof(TaskConfig));
cfg->baud = 115200;
xTaskCreate(vTask, "T", 256, cfg, 1, NULL);
任务里用完后释放:
cpp
void vTask(void *pv)
{
TaskConfig *cfg = (TaskConfig *)pv;
// 使用 cfg
vPortFree(cfg); // 用完就释放
vTaskDelete(NULL);
}
⚠️ 一定要保证:
- 任务启动前对象存在
- 任务不再用之前别释放
- 谁分配谁释放(或规则明确)
✅ 正确做法 3:如果参数是"只读常量"
cpp
const TaskConfig cfg = { .baud = 115200 };
xTaskCreate(vTask, "T", 256, (void *)&cfg, 1, NULL);
✅ 可以
⚠️ 但 const 不等于"不是栈变量",下面这种还是错的:
cpp
void f(void) {
const int x = 5;
xTaskCreate(t, "T", 256, (void*)&x, 1, NULL); // ❌
}
3️⃣ 传句柄(队列 / 信号量 / 定时器)
句柄本身通常就已经是指针:
cpp
QueueHandle_t q = xQueueCreate(10, sizeof(int));
xTaskCreate(vProducer, "prod", 256, q, 1, NULL);
任务里:
cpp
void vProducer(void *pv)
{
QueueHandle_t q = (QueueHandle_t)pv;
}
✅ 正确
因为队列本体在 FreeRTOS 堆里,句柄值一直有效
4️⃣ main() 里传参的特殊坑(STM32 / Cortex-M 很常见)
cpp
int main(void)
{
int x = 10;
xTaskCreate(t, "T", 256, &x, 1, NULL);
vTaskStartScheduler();
}
看起来没问题,但:
main()的栈 = MSP- 调度器启动后,MSP 被拿去当 中断栈
main栈帧随时被覆盖
所以 不要在 main / 普通函数里传局部变量地址。
5️⃣ 推荐实践总结
| 想传的内容 | 推荐方式 |
|---|---|
| ID / 索引 / 模式 | (void *)id |
| 固定配置结构体 | static / 全局 |
| 动态参数 | pvPortMalloc + 任务内 vPortFree |
| 队列 / 信号量句柄 | 直接传句柄值 |
| 局部变量地址 | ❌ 禁止 |
6️⃣ 一句话记忆
**FreeRTOS 只拷贝
void*这个值,不拷贝它指向的东西。**指针参数指向的对象,必须"活得够久"。