字節(jié)對齊
現(xiàn)在計算機系統(tǒng)的內(nèi)存都是按照字節(jié)劃分的,理論上對任何變量的訪問可以從任何地址開始挖藏,但是實際情況是訪問特定的變量經(jīng)常在特定的內(nèi)存地址訪問,這就需要各種類型的數(shù)據(jù)按照一定的規(guī)則在空間上進行排列,而不是順序排放仍稀,這就是對齊的概念拳芙;
比如在32bit system 下察藐,一個4字節(jié)的int,如果它的地址是0x00000004 (4 的倍數(shù))舟扎,那么它就是對齊的分飞;如果是0x00000002(非4 的倍數(shù)),那么它就是非對齊的
- 為什么要使用字節(jié)對齊浆竭?
字節(jié)對齊的根本原因是在于CPU訪問效率的問題浸须,上面的例子中如果地址時0x00000002,那么CPU 需要訪問兩次內(nèi)存,
第一次訪問0x000000002 ~ 0x00000003 得到一個2 byte 的內(nèi)容邦泄;
第二次訪問0x000000004 ~ 0x00000005 在得到一個2 byte的內(nèi)容删窒,組合得到整形數(shù)據(jù)
如果變量在對齊位置上,就可以一次取出數(shù)據(jù)顺囊,一些系統(tǒng)對對齊比較嚴格肌索,比如spar 系統(tǒng),取未對齊的數(shù)據(jù)就會發(fā)生錯誤特碳;在x86 上不會產(chǎn)生錯誤诚亚,只是效率下降;不同的對齊方式午乓,在內(nèi)存中存儲的方法可能不一樣站宗,合理利用字節(jié)對齊,可以有效節(jié)約存儲空間
結(jié)構(gòu)體對齊
結(jié)構(gòu)體對齊的規(guī)則首先要看有沒有用#pragma pack宏聲明益愈,使用這個預編譯指令可以改變對齊規(guī)則梢灭,
有宏定義的情況下結(jié)構(gòu)體自身的大小應為預編譯指定規(guī)定對齊的大小的整數(shù)倍,同時結(jié)構(gòu)體內(nèi)的成員的地址也會按照預編譯指定規(guī)定的大小對齊蒸其,#pragma pack 參數(shù)可以是 "1" "2" "4" "8" 或者 "16"
未使用pragma pack宏聲明
未使用pragma pack宏聲明的情況下敏释,遵循下面三個原則:
- 第一個成員的首地址假設為0
- 每個成員的首地址大小是其自身大小的整數(shù)倍
- 結(jié)構(gòu)體的總大小,是其成員中所包含的最大類型size大小的整數(shù)倍
示例一:
// struct = 1+2+4+8
struct demostruct {
// baseAddr length padding
uint8_t a; // addr 1 1
uint16_t b; // addr+2 2 0
uint32_t c; // addr+4 4 0
uint64_t d; // addr+8 8 0
};
- 上面的例子中如果沒有結(jié)構(gòu)體的內(nèi)存對齊摸袁,真實的大小為 1 + 2 + 4+ 8 = 15 字節(jié)
按照結(jié)構(gòu)體里成員地址需要是其自身大小的整數(shù)倍的原則 假定為結(jié)構(gòu)體首地址為 addr
- 第一個成員 a 字節(jié)首地址 addr钥顽,長度為 1
- 第二個成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則靠汁,地址必須是 2 的整數(shù)倍蜂大,所以在 a 后補充一個字節(jié),b的首地址變?yōu)?add + 2蝶怔,長度為 2
- 第三個成員 c 首地址理論上為 addr + 4奶浦,符合是自身長度整數(shù)倍的原則,長度為 4
- 第四個成員 d 首地址 addr + 8添谊,符合是自身長度整數(shù)倍的原則
- 結(jié)構(gòu)體大小為 16字節(jié)财喳,結(jié)構(gòu)體內(nèi)包含最大成員占用 8 字節(jié),符合上述原則
打印出的結(jié)構(gòu)體大小和各成員內(nèi)存地址如下:
struct_a size 16 a addr:008726E8 b:008726EA c addr:008726EC d addr:008726F0
示例二:
// struct = 1+8+1+1
struct demostruct_b {
// baseAddr length padding
// addr 1 7
// addr+8 8 0
// addr+16 1 0
// addr+17 1 0
uint8_t a;
uint64_t b;
uint8_t c;
uint8_t d;
};
- 上面的例子中如果沒有結(jié)構(gòu)體的內(nèi)存對齊,真實的大小為 1 + 1 + 8+ 1 = 11 字節(jié)
- 第一個成員 a 字節(jié)首地址 addr耳高,長度為 1
- 第二個成員 b 字節(jié)首地址理論上為 addr + 1扎瓶,但是按照地址對齊原則,地址必須是 8 的整數(shù)倍泌枪,所以在 a 后補充7個字節(jié)概荷,首地址變?yōu)?add + 8 長度為 8,
- 第三個成員 c 首地址為 addr + 16碌燕,長度為 1
- 第四個成員 d 首地址 addr + 17误证,長度為 1
- 結(jié)構(gòu)體大小為 18 字節(jié),結(jié)構(gòu)體內(nèi)包含最大成員占用 8 字節(jié)修壕,所以還需要在最后補充 6 字節(jié)愈捅,共 24 字節(jié)
打印出的結(jié)構(gòu)體大小和各成員內(nèi)存地址如下:
struct_b size 24 a addr:008726F8 b:00872700 c addr:00872708 addr:00872709
使用#pragma pack宏聲明
#pragma pack(push)
#pragma pack(4)
// struct = 1+8+1+1
struct demostruct_c {
// baseAddr length padding
// addr 1 3
// addr+4 8 0
// addr+12 1 0
// addr+13 1 0
uint8_t a;
uint64_t b;
uint8_t c;
uint8_t d;
};
#pragma pack(pop)
- 上面的例子中用宏定義 按照 4 字節(jié)的方式對齊
- 第一個成員 a 字節(jié)首地址 addr,長度為 1
- 第二個成員 b 字節(jié)首地址理論上為 addr + 1慈鸠,但是按照地址對齊原則蓝谨,地址必須是 4 的整數(shù)倍,所以在 a 后補充3個字節(jié)青团,首地址變?yōu)?add + 4 長度為 8譬巫,
- 第三個成員 c 首地址為 addr + 12,長度為 1
- 第四個成員 d 首地址 addr + 13督笆,長度為 1
- 結(jié)構(gòu)體大小為14 字節(jié)芦昔,不是 pack(4) 的整數(shù)倍,所以在最后補充 2 字節(jié)娃肿,共 16 字節(jié)
struct_c size 16 a addr:00872710 b:00872714 c addr:0087271C addr:0087271D
注意:
當#pragma pack 設定的 pack 值大于結(jié)構(gòu)體中最大成員的size 值時咕缎,應當以結(jié)構(gòu)體中最大成員的size作為 pack 的值
換句話說,應該以這兩者中較小的值作為 pack 的值
#pragma pack(push)
#pragma pack(8)
// struct = 1+4 +2
struct demostruct_d {
// baseAddr length padding
// addr 1 3
// addr+4 4 0
// addr+8 2 0
uint8_t a;
uint32_t c;
uint16_t b;
};
#pragma pack(pop)
驗證結(jié)果是12 字節(jié)咸作,不是以設定的 pack 值 8
*struct_d size 12 a addr:00EF2720 b:00EF2728 c addr:00EF2724