• 正文
  • 相关推荐
申请入驻 产业图谱

原来结构体大小还可以这么检查校验???

04/06 13:01
325
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

1、前言

相信不少朋友在编程的时候,都有用到过sizeof()关键词得到结构体的内存大小;在开发系统参数保存功能的时候,通过定义一个结构体,将所有的系统参数都作为结构体成员变量,然后保存。

结构体保存的数据都是二进制数据,非常适合作为 MCU 的参数储存方式,但是这种方式存在一个缺点:扩展性不高。

这种缺点一般通过结构体成员空间预留的方式能得到解决。

2、结构体预留

通常通过预留的方式进行后期的参数扩展,如:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve[6];????//?预留
}?TestParam_t;????/*?某模块参数?*/

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?预留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?预留
}?SystemParam_t;?/*?系统参数?*/

这种方式在预留位置扩展新的成员变量时,都要保证结构体大小不变,且内部的成员变量偏移位置不变,因为在增加新的成员变量时由于结构体填充的原因容易导致结构体发生填充,从而不小心改变了结构体大小,甚至改变了其他成员的偏移位置。

后果:在系统升级后读取参数时就会因为结构体大小和升级前的不一致或者部分变量内存偏移改变引发系统异常。

所以在这种情况下,每次增加新的变量后都要仔细算一下结构大小有没有改变,甚至推算里面的结构体成员相对偏移位置有没有变化。

每次新增参数,手动计算和校验 99% 可以检查出来,但是人总有粗心的时候(加班多了,状态不好…),且结构体存在填充,一不留神就以为没问题,提交代码,出版本(测试不一定能发现),给客户,升级后异常,客户投诉、扣工资(难啊….)

遇到这种问题后:难道编译器就没有在编译的时候检查这个大小或者结构体成员的偏移吗,每次手动计算校验好麻烦啊,一不留神还容易算错

哎,你别说,这种还真可以实现···

3、结构体大小检查

利用宏定义在编译期间自动检查结构体大小,在编译的时候就能将错误暴露出来,宏定义如下:

/**
*?@brief?检查结构体大小是否符合,在编译时会进行检查
*
*?@param?type?结构体类型
*?@param?size?结构体检查大小
*/
#define?TYPE_CHECK_SIZE(type,?size)
extern?int?sizeof_##type##_is_error?[!!(sizeof(type)==(size_t)(size))?-?1]

使用方式:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve[6];????//?预留
}?TestParam_t;????/*?某模块参数?*/

TYPE_CHECK_SIZE(TestParam_t,?8);?//?检查结构体的大小是否符合预期

在TestParam_t中增加一个变量,假设不小心预留大小写错了:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint16_t?testParam3;
uint8_t?reserve[5];????//?预留
}?TestParam_t;????/*?某模块参数?*/

TYPE_CHECK_SIZE(TestParam_t,?8);?//?检查结构体的大小是否符合预期

编译器报错内容(通过sizeof_TestParam_t_is_error就能定位是哪个结构体):

4、结构体成员相对偏移检查

利用宏定义在编译期间自动检查结构体中的成员变量偏移地址,在编译的时候就能将错误暴露出来,宏定义如下:

/**
*?@brief?检查结构体成员偏移位置是否符合,?在编译时会进行检查
*?@param?type?结构体类型
*?@param?member?结构体成员
*?@param?value?成员偏移
*/
#define?TYPE_MEMBER_CHECK_OFFSET(type,?member,?value)
extern?int?offset_of_##member##_in_##type##_is_error
[!!(__builtin_offsetof(type,?member)==((size_t)(value)))?-?1]

使用方式:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve[6];????//?预留
}?TestParam_t;????/*?某模块参数?*/

TYPE_MEMBER_CHECK_OFFSET(TestParam_t,?testParam2,?1);

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?预留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?预留
}?SystemParam_t;?/*?系统参数?*/

TYPE_MEMBER_CHECK_OFFSET(SystemParam_t,?tTestParam,?12);

在SystemParam_t中尝试修改成员变量tTestParam的偏移位置检查:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?预留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?预留
}?SystemParam_t;?/*?系统参数?*/

TYPE_MEMBER_CHECK_OFFSET(SystemParam_t,?tTestParam,?13);

编译时则报错:(通过offset_of_testParam2_in_TestParam_t_is_error就能定位是哪个结构体的哪个成员变量偏移位置不对了):

5、总结

上述宏定义检查方式是通过声明一个数组,检查正确则是数组[0],否则就是数组[-1],合理地利用编译器规则(前提是编译器支持定义数组[0])来检查结构体的大小和成员变量的偏移。

这个写法而且只占用文本大小,编译后不占内存!!!

关于这种方式的检查,你了解或者能理解多少呢?

相关推荐