C 語言學(xué)習(xí)(12) ---- C語言結(jié)構(gòu)體對齊

字節(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è)原則:

  1. 第一個(gè)成員的首地址假設(shè)為0
  2. 每個(gè)成員的首地址大小是其自身大小的整數(shù)倍
  3. 結(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
  1. 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
  2. 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 2 的整數(shù)倍,所以在 a 后補(bǔ)充一個(gè)字節(jié),b的首地址變?yōu)?add + 2,長度為 2
  3. 第三個(gè)成員 c 首地址理論上為 addr + 4,符合是自身長度整數(shù)倍的原則,長度為 4
  4. 第四個(gè)成員 d 首地址 addr + 8,符合是自身長度整數(shù)倍的原則
  5. 結(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

對齊方法01.jpg

示例二:

//  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é)
  1. 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
  2. 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 8 的整數(shù)倍,所以在 a 后補(bǔ)充7個(gè)字節(jié),首地址變?yōu)?add + 8 長度為 8,
  3. 第三個(gè)成員 c 首地址為 addr + 16,長度為 1
  4. 第四個(gè)成員 d 首地址 addr + 17,長度為 1
  5. 結(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

對齊方法02.jpg

使用#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é)的方式對齊
  1. 第一個(gè)成員 a 字節(jié)首地址 addr,長度為 1
  2. 第二個(gè)成員 b 字節(jié)首地址理論上為 addr + 1,但是按照地址對齊原則,地址必須是 4 的整數(shù)倍,所以在 a 后補(bǔ)充3個(gè)字節(jié),首地址變?yōu)?add + 4 長度為 8,
  3. 第三個(gè)成員 c 首地址為 addr + 12,長度為 1
  4. 第四個(gè)成員 d 首地址 addr + 13,長度為 1
  5. 結(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

pack對齊方法01.jpg

注意:
當(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容