副作用
在 C 语言的宏定义中,“副作用” 指的是一个表达式在求值过程中,除了返回一个值之外,还对程序状态(如变量值、内存内容、文件位置等)产生了持久的改变。例如,自增运算 ++、自减运算 --、赋值运算、函数调用等都可能产生副作用。
宏展开中的副作用问题
宏只是简单的文本替换,宏参数会在宏体的展开位置被原样替换多次。如果传入的参数本身是一个带有副作用的表达式(例如 pxList++),那么该表达式会被多次求值,导致意外的结果。

举例说明

假设有一个宏,它原本想获取链表的节点数并保存指针,但错误地多次使用了参数:

#define BAD_MACRO(list)   ( (list)->uxNumberOfItems + (list)->uxNumberOfItems )

如果调用 BAD_MACRO(pxList++),宏展开后变成:

( (pxList++)->uxNumberOfItems + (pxList++)->uxNumberOfItems )

这里 pxList++ 被执行了两次,pxList 被错误地增加了两次,而且第二次 pxList++ 时指针已经指向了下一个节点,逻辑完全错误。


FreeRTOS 中的防御性写法

listGET_OWNER_OF_NEXT_ENTRY 中:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
    List_t * const pxConstList = ( pxList ); \
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
    { \
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
    } \
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}

这里首先将参数 pxList 的值赋给一个局部常量指针 pxConstList。这有两个作用:

  1. 避免多次求值
    宏体中多次使用了 pxConstList(即对参数 pxList 的求值)。如果没有这一行,直接使用 pxList 的话,若用户不小心传入了带有副作用的表达式(如 pxList++),就会导致 pxList 被多次递增,产生严重的 bug。通过先保存值,确保了参数只被求值一次。

  2. 类型安全与可读性
    使用一个局部变量也提高了代码的可读性,并且在调试时更容易观察中间值。


为什么用了 const

const 修饰表示这个局部指针本身的值(即它所指向的地址)在宏体内不会被修改。这可以防止宏体内部意外地改变指针的值,进一步保证了安全性。实际上,宏体中确实需要修改 pxConstList->pxIndex,但指针本身指向的地址是固定的,所以用 const 是合理的。


总结

在编写宏时,如果宏体内需要多次使用某个参数,且该参数可能是一个带有副作用的表达式,那么最安全的做法就是将参数先求值并保存在一个临时变量中,然后在宏体中只使用这个临时变量。FreeRTOS 的这个宏正是遵循了这一最佳实践,避免了因宏展开导致的副作用重复计算问题。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐