字節(jié)對齊
現(xiàn)在計(jì)算機(jī)系統(tǒng)的內(nèi)存都是按照字節(jié)劃分的,理論上對任何變量的訪問可以從任何地址開始,但是實(shí)際情況是訪問特定的變量經(jīng)常在特定的內(nèi)存地址訪問,這就需要各種類型的數(shù)據(jù)按照一定的規(guī)則在空間上進(jìn)行排列,而不是順序排放,這就是對齊的概念;
比如在32bit system 下,一個(gè)4字節(jié)的int,如果它的地址是0x00000004 (4 的倍數(shù)),那么它就是對齊的;如果是0x00000002(非4 的倍數(shù)),那么它就是非對齊的
- 為什么要使用字節(jié)對齊?
字節(jié)對齊的根本原因是在于CPU訪問效率的問題,上面的例子中如果地址時(shí)0x00000002,那么CPU 需要訪問兩次內(nèi)存,
第一次訪問0x000000002 ~ 0x00000003 得到一個(gè)2 byte 的內(nèi)容;
第二次訪問0x000000004 ~ 0x00000005 在得到一個(gè)2 byte的內(nèi)容,組合得到整形數(shù)據(jù)
如果變量在對齊位置上,就可以一次取出數(shù)據(jù),一些系統(tǒng)對對齊比較嚴(yán)格,比如spar 系統(tǒng),取未對齊的數(shù)據(jù)就會(huì)發(fā)生錯(cuò)誤;在x86 上不會(huì)產(chǎn)生錯(cuò)誤,只是效率下降;不同的對齊方式,在內(nèi)存中存儲(chǔ)的方法可能不一樣,合理利用字節(jié)對齊,可以有效節(jié)約存儲(chǔ)空間
結(jié)構(gòu)體對齊
結(jié)構(gòu)體對齊的規(guī)則首先要看有沒有用#pragma pack宏聲明,使用這個(gè)預(yù)編譯指令可以改變對齊規(guī)則,
有宏定義的情況下結(jié)構(gòu)體自身的大小應(yīng)為預(yù)編譯指定規(guī)定對齊的大小的整數(shù)倍,同時(shí)結(jié)構(gòu)體內(nèi)的成員的地址也會(huì)按照預(yù)編譯指定規(guī)定的大小對齊,#pragma pack 參數(shù)可以是 "1" "2" "4" "8" 或者 "16"
未使用pragma pack宏聲明
未使用pragma pack宏聲明的情況下,遵循下面三個(gè)原則:
- 第一個(gè)成員的首地址假設(shè)為0
- 每個(gè)成員的首地址大小是其自身大小的整數(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)存對齊,真實(shí)的大小為 1 + 2 + 4+ 8 = 15 字節(jié)
按照結(jié)構(gòu)體里成員地址需要是其自身大小的整數(shù)倍的原則 假定為結(jié)構(gòu)體首地址為 addr
- 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
- 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 2 的整數(shù)倍,所以在 a 后補(bǔ)充一個(gè)字節(jié),b的首地址變?yōu)?add + 2,長度為 2
- 第三個(gè)成員 c 首地址理論上為 addr + 4,符合是自身長度整數(shù)倍的原則,長度為 4
- 第四個(gè)成員 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)存對齊,真實(shí)的大小為 1 + 1 + 8+ 1 = 11 字節(jié)
- 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
- 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 8 的整數(shù)倍,所以在 a 后補(bǔ)充7個(gè)字節(jié),首地址變?yōu)?add + 8 長度為 8,
- 第三個(gè)成員 c 首地址為 addr + 16,長度為 1
- 第四個(gè)成員 d 首地址 addr + 17,長度為 1
- 結(jié)構(gòu)體大小為 18 字節(jié),結(jié)構(gòu)體內(nèi)包含最大成員占用 8 字節(jié),所以還需要在最后補(bǔ)充 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é)的方式對齊
- 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
- 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 4 的整數(shù)倍,所以在 a 后補(bǔ)充3個(gè)字節(jié),首地址變?yōu)?add + 4 長度為 8,
- 第三個(gè)成員 c 首地址為 addr + 12,長度為 1
- 第四個(gè)成員 d 首地址 addr + 13,長度為 1
- 結(jié)構(gòu)體大小為14 字節(jié),不是 pack(4) 的整數(shù)倍,所以在最后補(bǔ)充 2 字節(jié),共 16 字節(jié)
struct_c size 16 a addr:00872710 b:00872714 c addr:0087271C addr:0087271D

注意:
當(dāng)#pragma pack 設(shè)定的 pack 值大于結(jié)構(gòu)體中最大成員的size 值時(shí),應(yīng)當(dāng)以結(jié)構(gòu)體中最大成員的size作為 pack 的值
換句話說,應(yīng)該以這兩者中較小的值作為 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)
驗(yàn)證結(jié)果是12 字節(jié),不是以設(shè)定的 pack 值 8
*struct_d size 12 a addr:00EF2720 b:00EF2728 c addr:00EF2724