• 正文
    • 1、前言
    • 2、結(jié)構(gòu)體預(yù)留
    • 3、結(jié)構(gòu)體大小檢查
    • 4、結(jié)構(gòu)體成員相對偏移檢查
    • 5、總結(jié)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

原來結(jié)構(gòu)體大小還可以這么檢查校驗???

04/06 13:01
151
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

1、前言

相信不少朋友在編程的時候,都有用到過sizeof()關(guān)鍵詞得到結(jié)構(gòu)體的內(nèi)存大?。辉陂_發(fā)系統(tǒng)參數(shù)保存功能的時候,通過定義一個結(jié)構(gòu)體,將所有的系統(tǒng)參數(shù)都作為結(jié)構(gòu)體成員變量,然后保存。

結(jié)構(gòu)體保存的數(shù)據(jù)都是二進(jìn)制數(shù)據(jù),非常適合作為 MCU 的參數(shù)儲存方式,但是這種方式存在一個缺點:擴(kuò)展性不高。

這種缺點一般通過結(jié)構(gòu)體成員空間預(yù)留的方式能得到解決。

2、結(jié)構(gòu)體預(yù)留

通常通過預(yù)留的方式進(jìn)行后期的參數(shù)擴(kuò)展,如:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve[6];????//?預(yù)留
}?TestParam_t;????/*?某模塊參數(shù)?*/

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?預(yù)留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?預(yù)留
}?SystemParam_t;?/*?系統(tǒng)參數(shù)?*/

這種方式在預(yù)留位置擴(kuò)展新的成員變量時,都要保證結(jié)構(gòu)體大小不變,且內(nèi)部的成員變量偏移位置不變,因為在增加新的成員變量時由于結(jié)構(gòu)體填充的原因容易導(dǎo)致結(jié)構(gòu)體發(fā)生填充,從而不小心改變了結(jié)構(gòu)體大小,甚至改變了其他成員的偏移位置。

后果:在系統(tǒng)升級后讀取參數(shù)時就會因為結(jié)構(gòu)體大小和升級前的不一致或者部分變量內(nèi)存偏移改變引發(fā)系統(tǒng)異常。

所以在這種情況下,每次增加新的變量后都要仔細(xì)算一下結(jié)構(gòu)大小有沒有改變,甚至推算里面的結(jié)構(gòu)體成員相對偏移位置有沒有變化。

每次新增參數(shù),手動計算和校驗 99% 可以檢查出來,但是人總有粗心的時候(加班多了,狀態(tài)不好…),且結(jié)構(gòu)體存在填充,一不留神就以為沒問題,提交代碼,出版本(測試不一定能發(fā)現(xiàn)),給客戶,升級后異常,客戶投訴、扣工資(難啊….)

遇到這種問題后:難道編譯器就沒有在編譯的時候檢查這個大小或者結(jié)構(gòu)體成員的偏移嗎,每次手動計算校驗好麻煩啊,一不留神還容易算錯

哎,你別說,這種還真可以實現(xiàn)···

3、結(jié)構(gòu)體大小檢查

利用宏定義在編譯期間自動檢查結(jié)構(gòu)體大小,在編譯的時候就能將錯誤暴露出來,宏定義如下:

/**
*?@brief?檢查結(jié)構(gòu)體大小是否符合,在編譯時會進(jìn)行檢查
*
*?@param?type?結(jié)構(gòu)體類型
*?@param?size?結(jié)構(gòu)體檢查大小
*/
#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];????//?預(yù)留
}?TestParam_t;????/*?某模塊參數(shù)?*/

TYPE_CHECK_SIZE(TestParam_t,?8);?//?檢查結(jié)構(gòu)體的大小是否符合預(yù)期

在TestParam_t中增加一個變量,假設(shè)不小心預(yù)留大小寫錯了:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint16_t?testParam3;
uint8_t?reserve[5];????//?預(yù)留
}?TestParam_t;????/*?某模塊參數(shù)?*/

TYPE_CHECK_SIZE(TestParam_t,?8);?//?檢查結(jié)構(gòu)體的大小是否符合預(yù)期

編譯器報錯內(nèi)容(通過sizeof_TestParam_t_is_error就能定位是哪個結(jié)構(gòu)體):

4、結(jié)構(gòu)體成員相對偏移檢查

利用宏定義在編譯期間自動檢查結(jié)構(gòu)體中的成員變量偏移地址,在編譯的時候就能將錯誤暴露出來,宏定義如下:

/**
*?@brief?檢查結(jié)構(gòu)體成員偏移位置是否符合,?在編譯時會進(jìn)行檢查
*?@param?type?結(jié)構(gòu)體類型
*?@param?member?結(jié)構(gòu)體成員
*?@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];????//?預(yù)留
}?TestParam_t;????/*?某模塊參數(shù)?*/

TYPE_MEMBER_CHECK_OFFSET(TestParam_t,?testParam2,?1);

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?預(yù)留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?預(yù)留
}?SystemParam_t;?/*?系統(tǒng)參數(shù)?*/

TYPE_MEMBER_CHECK_OFFSET(SystemParam_t,?tTestParam,?12);

在SystemParam_t中嘗試修改成員變量tTestParam的偏移位置檢查:

typedef?struct
{
uint8_t?testParam;
uint8_t?testParam2;
uint8_t?reserve1[10];???//?預(yù)留
TestParam_t?tTestParam;
uint16_t?testParam3;
uint8_t?reserve2[10];???//?預(yù)留
}?SystemParam_t;?/*?系統(tǒng)參數(shù)?*/

TYPE_MEMBER_CHECK_OFFSET(SystemParam_t,?tTestParam,?13);

編譯時則報錯:(通過offset_of_testParam2_in_TestParam_t_is_error就能定位是哪個結(jié)構(gòu)體的哪個成員變量偏移位置不對了):

5、總結(jié)

上述宏定義檢查方式是通過聲明一個數(shù)組,檢查正確則是數(shù)組[0],否則就是數(shù)組[-1],合理地利用編譯器規(guī)則(前提是編譯器支持定義數(shù)組[0])來檢查結(jié)構(gòu)體的大小和成員變量的偏移。

這個寫法而且只占用文本大小,編譯后不占內(nèi)存?。。?/p>

關(guān)于這種方式的檢查,你了解或者能理解多少呢?

相關(guān)推薦