void * 是 C 语言中的通用指针类型,它可以指向任意类型的数据。在 FreeRTOS 的链表实现中,它被广泛用于实现类型无关的通用数据结构。


1. 为什么需要 void *

在 FreeRTOS 中,同一个链表结构(List_t)需要能够挂载不同类型的对象,比如:

  • 任务控制块(TCB_t
  • 软件定时器控制块
  • 事件组等待节点

如果链表节点的 pvOwner 成员固定为某种具体类型(比如 TCB_t *),那么链表就只能挂载任务,缺乏通用性。而使用 void *,就可以存储任何类型的指针,实现一个链表结构服务于多种内核对象。


2. 在宏中的具体用法

(1)设置拥有者:listSET_LIST_ITEM_OWNER
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) \
    ((pxListItem)->pvOwner = (void*)(pxOwner))
  • pxOwner 通常是一个具体类型的指针(如 TCB_t *),但链表节点只保存 void * 类型。
  • 强制转换为 void * 是为了消除编译器关于类型不匹配的警告,明确表示“我们将这个指针视为通用指针存储起来”。
(2)获取拥有者:listGET_LIST_ITEM_OWNER
#define listGET_LIST_ITEM_OWNER( pxListItem ) \
    ( ( pxListItem )->pvOwner )
  • 返回的是 void *,使用时需要由调用者强制转换回原本的类型,例如:
    TCB_t *pxTCB = (TCB_t *) listGET_LIST_ITEM_OWNER( pxListItem );
    
  • 这种显式转换让代码的意图清晰,同时保持链表模块的通用性。
(3)比较指针是否指向哨兵节点:listGET_OWNER_OF_NEXT_ENTRY 中的 if 语句
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )
  • pxIndexListItem_t * 类型,指向链表节点。
  • &(pxConstList->xListEnd)ListItem_t * 类型,指向哨兵节点。
  • 将两者都转换为 void * 再比较,是为了只比较地址值,而不关心它们的原始类型。这样可以避免不同指针类型之间的直接比较产生的编译警告或错误。

3. void * 的隐式与显式转换

  • 任何类型的指针都可以隐式转换void *(C 语言标准允许)。
  • void * 转换回具体类型指针时,需要显式强制转换,否则编译器会报警告或错误。

在 FreeRTOS 中,经常可以看到这样的模式:

// 存储
listSET_LIST_ITEM_OWNER( &xListItem, pxTCB );   // 隐式转换

// 取出
TCB_t *pxTCB = (TCB_t *) listGET_LIST_ITEM_OWNER( &xListItem );

4. 为什么在比较时也要强制转换?

listGET_OWNER_OF_NEXT_ENTRY 宏中,pxConstList->pxIndex&(pxConstList->xListEnd) 的类型都是 ListItem_t *,按理说直接比较也是合法的。但 FreeRTOS 仍然使用了 (void *) 强制转换,主要原因有两点:

  1. 防止因类型限定符(如 const)导致的编译警告
    在某些编译环境中,如果其中一个指针带有 const 限定而另一个没有,直接比较可能会产生警告。转换为 void * 后忽略掉限定符,让比较纯粹是地址比较。

  2. 代码风格的一致性
    FreeRTOS 在涉及指针比较的地方习惯使用 (void *) 转换,明确表示“这里只比较地址”,提高了可移植性。


总结

void * 是 FreeRTOS 实现通用链表的关键手段。它允许链表节点持有任意类型的对象指针,使内核代码能够复用同一套链表操作宏来管理任务、定时器、事件等不同内核对象。在宏中使用 void * 进行强制转换,不仅消除了类型相关的编译警告,也清晰地表达了“我们只关心地址或通用指针”的设计意图。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐