来源:裸机思维 【说在前面的话】
【人人都可以学会的“表格”】
typedef struct <表格名称>_item_t <表格名称>_item_t;
struct <表格名称>_item_t { // 每条记录中的内容 };
以消息地图为例,一个常见的记录结构体定义如下: typedef struct msg_item_t msg_item_t;
struct msg_item_t { uint8_t chID; //!< 指令 uint8_t chAccess; //!< 访问权限检测 uint16_t hwValidDataSize; //!< 数据长度要求 bool (*fnHandler)(msg_item_t *ptMSG, void *pData, uint_fast16_t hwSize); };
const msg_item_t c_tMSGTable[] = { [0] = { .chID = 0, .fnHandler = NULL, }, [1] = { ... }, ... }; 上面写法的好处主要是方便我们偷懒,减少不必要的“数数”过程。那么,我们要如何知道一个表格中数组究竟有多少个元素呢?别慌,我们有 sizeof():
【表格的访问(遍历)】 由于表格的本质是结构体数组,因此,针对表格最常见的操作就是遍历(搜索)了。还以前面消息地图为例子: static volatile uint8_t s_chCurrentAccessPermission;
/*! \brief 搜索消息地图,并执行对应的处理程序 *! \retval false 消息不存在或者消息处理函数觉得内容无效 *! \retval true 消息得到了正确的处理 */ bool search_msgmap(uint_fast8_t chID, void *pData, uint_fast16_t hwSize) { for (int n = 0; n < dimof(c_tMSGTable); n++) { msg_item_t *ptItem = &c_tMSGTable[n]; if (chID != ptItem->chID) { continue; } if (!(ptItem->chAccess & s_chCurrentAccessPermission)) { continue; //!< 当前的访问属性没有一个符合要求 } if (hwSize < ptItem->hwSize) { continue; //!< 数据太小了 } if (NULL == ptItem->fnHandler) { continue; //!< 无效的指令?(不应该发生) } //! 调用消息处理函数 return ptItem->fnHandler(ptItem, pData, hwSize); } return false; //!< 没找到对应的消息 }
前面的例子为我们展示表格使用的大体细节,对很多嵌入式应用场景来说,已经完全够用了。但爱思考的小伙伴一定已经发现了问题: 如果我的系统中有多个消息地图(每个消息地图中消息数量是不同的),我改怎么复用代码呢? 为了照顾还一脸懵逼的小伙伴,我把这个问题给大家翻译翻译:
一个头疼医头脚疼医脚的修改方案呼之欲出:
假设我们有多个消息地图,对应不同的工作模式: const msg_item_t c_tMSGTableUserMode[] = { ... }; const msg_item_t c_tMSGTableSetupMode[] = { ... };
const msg_item_t c_tMSGTableDebugMode[] = { ... };
const msg_item_t c_tMSGTableFactoryMode[] = { ... };
前面我们说过,表格的定义分两个部分:
其中,关于容器的定义,我们说过,数组是容器的最简单形式。那么容器定义的完全体是怎样的呢? “还是结构体”! 是的,表格条目的本质是结构体,表格容器的本质也是一个结构体: typedef struct <表格名称>_item_t <表格名称>_item_t;
struct <表格名称>_item_t { // 每条记录中的内容 };
typedef struct <表格名称>_t <表格名称>_t;
struct <表格名称>_t { uint16_t hwItemSize; uint16_t hwCount; <表格名称>_item_t *ptItems; };
这个hwItemSize其实是来凑数的,因为32位系统中指针4字节对齐的缘故,2字节的hwCount横竖会产生2字节的气泡。不理解这一点的小伙伴,可以参考文章《漫谈C变量——对齐(3)》
bool search_msgmap(msgmap_t *ptMSGMap, uint_fast8_t chID, void *pData, uint_fast16_t hwSize) { for (int n = 0; n < ptMSGMap->hwCount; n++) { msg_item_t *ptItem = &(ptMSGMap->ptItems[n]); if (chID != ptItem->chID) { continue; } ... //! 调用消息处理函数 return ptItem->fnHandler(ptItem, pData, hwSize); } return false; //!< 没找到对应的消息 } “是的……是稍微优雅一点……然后呢?” “就这!?就这?!” 别急,下面才是见证奇迹的时刻。 在前面的例子中,我们注意到表格的初始化是分两部分进行的:
那么,我们可不可以把它们合二为一呢?这样:
要做到这一点,我们可以使用一个类似“匿名数组”的功能: const msgmap_t c_tMSGMapUserMode = { .hwItemSize = sizeof(msg_item_t), .hwCount = dimof(c_tMSGTableUserMode), .ptItems = const msg_item_t c_tMSGTableUserMode[] = { [0] = { .chID = 0, .fnHandler = NULL, }, [1] = { ... }, ... }, };
![]() 其实,这不是什么“黑魔法”,而是一个广为使用的GNU扩展,被称为“复合式描述(Compound literal)”,本质上就是一种以“省略”数组或结构体名称的方式来初始化数组或结构体的语法结构。具体语法介绍,小伙伴们可以参考这篇文章《C语言语法中匿名的最高境界》。 眼尖的小伙伴也许已经发现了问题:既然我们省略了变量名,那么如何通过 dimof() 来获取数组元素的个数呢? 少侠好眼力! 解决方法不仅有,而且简单粗暴: const msgmap_t c_tMSGMapUserMode = { .hwItemSize = sizeof(msg_item_t), .hwCount = dimof((msg_item_t []){ [0] = { .chID = 0, .fnHandler = NULL, }, [1] = { ... }, ... }), .ptItems = (msg_item_t []){ [0] = { .chID = 0, .fnHandler = NULL, }, [1] = { ... }, ... }, }; 所以说?…… 为了优雅的初始化…… 我们要把同样的内容写两次?!! 手写的确挺愚蠢,但宏可以啊!
const msgmap_t c_tMSGMapUserMode = { impl_table(msg_item_t, [0] = { .chID = 0, .fnHandler = NULL, }, [1] = { ... }, ... ), };
现在当然就要改为如下的形式了: const msgmap_t c_tMSGMapUserMode = { impl_table(msg_item_t, ... ), };
const msgmap_t c_tMSGMapSetupMode = { impl_table(msg_item_t, ... ), };
const msgmap_t c_tMSGMapDebugMode = { impl_table(msg_item_t, ... ), };
const msgmap_t c_tMSGMapFactoryMode = { impl_table(msg_item_t, ... ), }; 但……它们不都是 msgmap_t 类型的么?为啥不做一个数组呢?
是不是有点意思了?再进一步,我们完全可以做一个新的表格,表格的元素就是 msgmap_t 呀? typedef struct cmd_modes_t cmd_modes_t;
struct cmd_modes_t { uint16_t hwItemSize; uint16_t hwCount; msgmap_t *ptItems; }; 然后就可以开始套娃咯:
【差异化……】
extern const cmd_modes_t c_tCMDModes;
bool frame_process_backend(comm_mode_t tWorkMode, uint_fast8_t chID, void *pData, uint_fast16_t hwSize) { bool bHandled = false; if (tWorkMode > FACTORY_MODE) { return false; } return search_msgmap( &(c_tCMDModes.ptItems[tWorkMode]), chID, pData, hwSize); } 是不是特别优雅?
extern bool msgmap_user_mode_handler(msgmap_t *ptMSGMap, uint_fast8_t chID, void *pData, uint_fast16_t hwSize);
extern bool msgmap_debug_mode_handler(msgmap_t *ptMSGMap, uint_fast8_t chID, void *pData, uint_fast16_t hwSize);
const cmd_modes_t c_tCMDModes = { impl_table(msgmap_t, [USER_MODE] = { impl_table(msg_item_t, ... ), .fnHandler = &msgmap_user_mode_handler, }, [SETUP_MODE] = { impl_table(msg_item_t, ... ), .fnHandler = NULL; //!< 使用默认的处理函数 }, [DEBUG_MODE] = { impl_table(msg_item_t, ... ), .fnHandler = &msgmap_debug_mode_handler, }, [FACTORY_MODE] = { impl_table(msg_item_t, ... ), //.fnHandler = NULL 什么都不写,就是NULL(0) }, ), };
|
|