1、前言
相信不少朋友在編程的時候,都有用到過sizeof()關鍵詞得到結構體的內存大?。辉陂_發(fā)系統(tǒng)參數保存功能的時候,通過定義一個結構體,將所有的系統(tǒng)參數都作為結構體成員變量,然后保存。
結構體保存的數據都是二進制數據,非常適合作為 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;?/*?系統(tǒng)參數?*/
這種方式在預留位置擴展新的成員變量時,都要保證結構體大小不變,且內部的成員變量偏移位置不變,因為在增加新的成員變量時由于結構體填充的原因容易導致結構體發(fā)生填充,從而不小心改變了結構體大小,甚至改變了其他成員的偏移位置。
后果:在系統(tǒng)升級后讀取參數時就會因為結構體大小和升級前的不一致或者部分變量內存偏移改變引發(fā)系統(tǒng)異常。
所以在這種情況下,每次增加新的變量后都要仔細算一下結構大小有沒有改變,甚至推算里面的結構體成員相對偏移位置有沒有變化。
每次新增參數,手動計算和校驗 99% 可以檢查出來,但是人總有粗心的時候(加班多了,狀態(tài)不好…),且結構體存在填充,一不留神就以為沒問題,提交代碼,出版本(測試不一定能發(fā)現),給客戶,升級后異常,客戶投訴、扣工資(難啊….)
遇到這種問題后:難道編譯器就沒有在編譯的時候檢查這個大小或者結構體成員的偏移嗎,每次手動計算校驗好麻煩啊,一不留神還容易算錯
哎,你別說,這種還真可以實現···
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;?/*?系統(tǒng)參數?*/
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;?/*?系統(tǒng)參數?*/
TYPE_MEMBER_CHECK_OFFSET(SystemParam_t,?tTestParam,?13);
編譯時則報錯:(通過offset_of_testParam2_in_TestParam_t_is_error就能定位是哪個結構體的哪個成員變量偏移位置不對了):
5、總結
上述宏定義檢查方式是通過聲明一個數組,檢查正確則是數組[0],否則就是數組[-1],合理地利用編譯器規(guī)則(前提是編譯器支持定義數組[0])來檢查結構體的大小和成員變量的偏移。
這個寫法而且只占用文本大小,編譯后不占內存?。。?/p>
關于這種方式的檢查,你了解或者能理解多少呢?