ToB企服应用市场:ToB评测及商务社交产业平台

标题: 当保存参数使用结构体时必备的开发技巧方式 [打印本页]

作者: 乌市泽哥    时间: 2022-8-23 15:41
标题: 当保存参数使用结构体时必备的开发技巧方式
1、前言

想必做嵌入式产品开发都遇到过设备需要保存参数,常用的方式就是按照结构体的方式管理参数,保存时将整个结构体数据保存在 Flash 中,方便下次读取。
1.1、目的

本文时分析嵌入式/单片机中参数保存的几种方式的优点和缺点(仅针对单片机/嵌入式开发而言),同时针对以结构体的方式解决一些弊端问题(重点在第 3 节)。
2、参数保存格式

2.1、结构体格式

该方式是嵌入式/单片机中开发最常用的,将所有的系统参数通过结构体的方式定义,然后保存数据,介绍一下该方式的优缺点。
储存方式:二进制 bin 文件格式
优点:
缺点:
改进措施:
结构体增加预留定义,若之后需要新增参数,则在预留空间新增即可,能在一定程度上解决扩展性差的问题,即新增不影响原有的结构体大小和其他成员变量的位置,删除恢复成预留即可。
为啥说只能在一定程度上解决该问题,因为之后的升级某些模块可能很长时间或者从不需要增加新的参数,这种势必就会造成内存的无效占用,或者有些模块频繁增加参数导致预留大小不够等问题,只能在前期设计时多加思考预留的分配情况(毕竟内存只有那么大)
  1. /*****************************
  2.            改进之前
  3. *****************************/
  4. typedef struct
  5. {
  6.     uint8_t testParam;
  7.     uint8_t testParam2;
  8. } TestParam_t;    /* 某模块参数 */
  9. typedef struct
  10. {
  11.     uint8_t testParam;
  12.     uint8_t testParam2;
  13.     TestParam_t tTestParam;
  14. } SystemParam_t; /* 系统参数 */
  15. /*****************************
  16.            改进之后
  17. *****************************/
  18. typedef struct
  19. {
  20.     uint8_t testParam;
  21.     uint8_t testParam2;
  22.     uint8_t reserve[6];    // 预留
  23. } TestParam_t;    /* 某模块参数 */
  24. typedef struct
  25. {
  26.     uint8_t testParam;
  27.     uint8_t testParam2;
  28.     TestParam_t tTestParam;
  29.     uint8_t reserve[50];   // 预留
  30. } SystemParam_t; /* 系统参数 */
复制代码
2.2、JSON格式

最近Json格式很是流行使用,特别是数据交换中用的很多,但是它也可以用来保存参数使用,JSON 的是 “{键:值}” 的方式。
储存方式:字符串格式,即文本的形式
优点:
缺点:
  1. {
  2.     "SYS":
  3.     {
  4.         "testParam" : 2,
  5.         "testParam2" : 5,
  6.         "tTestParam":
  7.         {
  8.             "testParam" : 2,
  9.             "testParam2" : 5
  10.         }
  11.     }
  12. }
  13. //压缩字符串为:
  14. {"SYS":{"testParam":2,"testParam2":5,"tTestParam":{"testParam":2,"testParam2":5}}}
复制代码
2.3、键值格式

和上述的 JSON 格式很类似,都是键值对的格式,但是比JSON简单
储存方式:字符串格式,即文本的形式
优点:
缺点:
  1. testParam=2
  2. testParam2=5
  3. T_testParam=2
  4. T_testParam2=5
复制代码
2.4 其他

还有其他,如 xml (类似JSON)等,就不多介绍了
3、编译器检查结构体的大小和成员变量的偏移

在第 2 节中介绍了关于参数保存的三种方式,但是对于嵌入式单片机开发而言,Flash 大小不富裕,所以通常都是通过二进制的形式保存的,所以这节重点解决结构体管理保存参数的扩展性问题。
先说一下痛点(虽然对扩展性问题做了改进措施,除了前面讲到的问题,还有其他痛点,虽不算问题,但是一旦出现往往最要命)
每次新增参数,手动计算和校验 99% 可以检查出来,但是人总有粗心的时候(加班多了,状态不好...),且结构体存在填充,一不留神就以为没问题,提交代码,出版本(测试不一定能发现),给客户,升级后异常,客户投诉、扣工资(难啊....)
遇到这种问题后:难道编译器就不能在编译的时候检查这个大小或者结构体成员的偏移吗,每次手动计算校验好麻烦啊,一不留神还容易算错 # _ #
按照正常情况,编译器可不知道你写的结构体大小和你想要的多大,所以检查不出来(天啊,崩溃了0.0....)
别急,有另类的方式可以达到这种功能,在编译时让编译器为你检查,而且准确性 100%(当然,这个添加新参数时你还得简单根据新增的参数大小减少预留的大小,这个是必须要的)
见代码:
  1. /**
  2.   * @brief 检查结构体大小是否符合
  3.   *        在编译时会进行检查
  4.   * @param type 结构体类型
  5.   * @param size 结构体检查大小
  6.   */
  7. #define TYPE_CHECK_SIZE(type, size) extern int sizeof_##type##_is_error [!!(sizeof(type)==(size_t)(size)) - 1]
  8. /**
  9.   * @brief 结构体成员
  10.   * @param type   结构体类型
  11.   * @param member 成员变量
  12.   */
  13. #define TYPE_MEMBER(type, member) (((type *)0)->member)
  14. /**
  15.   * @brief 检查结构体成员大小是否符合
  16.   *        在编译时会进行检查
  17.   * @param type 结构体类型
  18.   * @param member 结构体类型
  19.   * @param size 结构体检查大小
  20.   */
  21. #define TYPE_MEMBER_CHECK_SIZE(type, member, size) extern int sizeof_##type##_##member##_is_error \
  22.     [!!(sizeof(TYPE_MEMBER(type, member))==(size_t)(size)) - 1]
  23. /**
  24.   * @brief 检查结构体中结构体成员大小是否符合
  25.   *        在编译时会进行检查
  26.   * @param type 结构体类型
  27.   * @param member 结构体类型
  28.   * @param size 结构体检查大小
  29.   */
  30. #define TYPE_CHILDTYPE_MEMBER_CHECK_SIZE(type, childtype, member, size) extern int sizeof_##type##_##childtype##_##member##_is_error \
  31.     [!!(sizeof(TYPE_MEMBER(type, childtype.member))==(size_t)(size)) - 1]
  32. /**
  33.   * @brief 检查结构体成员偏移位置是否符合
  34.   *        在编译时会进行检查
  35.   * @param type 结构体类型
  36.   * @param member 结构体成员
  37.   * @param value 成员偏移
  38.   */
  39. #define TYPE_MEMBER_CHECK_OFFSET(type, member, value) \
  40.          extern int offset_of_##member##_in_##type##_is_error \
  41.         [!!(__builtin_offsetof(type, member)==((size_t)(value))) - 1]
  42. /**
  43.   * @brief 检查结构体成员偏移位置是否符合
  44.   *        在编译时会进行检查
  45.   * @param type 结构体类型
  46.   * @param member 结构体成员
  47.   * @param value 成员偏移
  48.   */
  49. #define TYPE_CHILDTYPE_MEMBER_CHECK_OFFSET(type, childtype, member, value) \
  50.          extern int offset_of_##member##_in_##type##_##childtype##_is_error \
  51.         [!!(__builtin_offsetof(type, childtype.member)==((size_t)(value))) - 1]
复制代码
通过以上代码,就能解决这个问题,这个写法只占用文本大小,编译后不占内存!!!
用法:
  1. typedef struct
  2. {
  3.     uint8_t testParam;
  4.     uint8_t testParam2;
  5.     uint8_t reserve[6];    // 预留
  6. } TestParam_t;    /* 某模块参数 */
  7. TYPE_CHECK_SIZE(TestParam_t, 8); // 检查结构体的大小是否符合预期
  8. typedef struct
  9. {
  10.     uint8_t testParam;
  11.     uint8_t testParam2;
  12.     TestParam_t tTestParam;
  13.     uint8_t reserve[54];   // 预留
  14. } SystemParam_t; /* 系统参数 */
  15. TYPE_CHECK_SIZE(SystemParam_t, 64); // 检查结构体的大小是否符合预期
  16. TYPE_MEMBER_CHECK_OFFSET(SystemParam_t, tTestParam, 2); // 检查结构体成员tTestParam偏移是否符合预期
复制代码
假设新增了参数,预留写错了,导致结构体的大小不符合,则编译时报错,且提示内容也能快速定位问题。

关于这种方式的检查,你了解或者能理解多少呢?有兴趣的朋友可以留下你的评论。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4