1. 程式人生 > >記憶體對齊問題(結構體,聯合體,位段)

記憶體對齊問題(結構體,聯合體,位段)

結構體

typedef struct A
{
    char c1;
    char c2;
    int i;
}A;
typedef struct B
{
    char c1;
    int i;
    char c2;
}B;
typedef struct C
{
    int i;
    char c1;
    char c2;
}C;

對於結構體A,B,C.它們具有同樣的結構體成員,只是調換了先後順序,我們來觀察它們的大小是否相同?

int main()
{
    printf("結構體A:%d\n",sizeof(A));
    printf("結構體B:%d
\n"
,sizeof(B)); printf("結構體C:%d\n",sizeof(C)); return 0; }

這裡寫圖片描述
結果顯示在sizeof計算結構體大小時,經常得到的值比結構體成員所佔記憶體總和要大,這是因為成員在儲存時有對齊的規則。
結構體對齊的規則
1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第一個資料成員放在offset為0的地方,以後每個資料成員的對齊按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。
3、結合1、2可推斷:當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。
先來剖析一下上述結構體的內部儲存結構:
這裡寫圖片描述


以A為例進行分析:首先將結構體的第一個資料成員c1放在0偏移處;下來判斷1偏移處是不是第二個成員c2的對齊數(1)的整數倍,是,直接填充
(佔用一個位元組);最後判斷2偏移處是不是第三個成員i的對齊數(4)的整倍數,不是,向後偏移2個位元組(2-3,浪費兩個位元組),偏移到第三個成員的對齊數(4)的1倍處,將int型資料填充,向後偏移4個位元組。

根據上例我們也不難得出結論:根據邊界對齊要求一般降序排列結構成員可以最大限度地減少結構儲存中浪費的記憶體空間。

為什麼存在記憶體對齊?
一般來說,該用多大的空間就開闢多大的記憶體就好了,像strcutA來說,兩個char型別和一個int型別的變數,大小加起來不應該是6位元組大小,為什麼結果會是8?那多出的兩個位元組不是被浪費了嗎?這裡便是為了求得記憶體對齊中以空間換時間的效果,為了訪問未對齊的記憶體,處理器需做兩次訪問,若遵循記憶體對齊原則只需訪問依次。
結構體記憶體對齊特殊情況分析


(1)結構體中不存在變數

typedef struct D
{
}D;
int main()
{
    printf("結構體D:%d\n",sizeof(D));
    return 0;
}

VS2013下執行可以通過:
這裡寫圖片描述
系統認為雖然結構體中沒有任何變數,但只要建立結構體就分配一個位元組,以建立存在感(月銷售業績為0的售樓小姐起碼也有底薪,雖然很少,但有的公司也會很苛刻,沒有業績的話底薪也甭想拿)正常情況下結構體至少要存在一個成員。
VS2008下執行不能通過:
這裡寫圖片描述
(2)結構體中巢狀結構體

typedef struct E
{
    char c;
    double d;
}E;

typedef struct F
{
    int i;
    char a[3];
    E e;
}F;
int main()
{
    printf("結構體D:%d\n",sizeof(F));
    return 0;
}

測試結果是24
這裡寫圖片描述
(3)結構體中存在柔性陣列
結構體中最後一個元素允許是未知大小的陣列。

typedef struct G
{
    int i;
    int a[0];
}G;

int main()
{
    printf("結構體G:%d\n",sizeof(G));
    return 0;
}

測試發現結構體G的大小為4,說明系統沒有為陣列a[0]分配空間,既然系統不給,那我們自己手動分配
這裡寫圖片描述
我們明明為它分配了10個整形大小空間為什麼結果仍然是4呢?這就要從柔性陣列的特性說起:定義結構體的時候我們知道柔性陣列只是一個允許是未知大小的陣列,它是用來擴充套件結構體大小的,它自己和結構體並沒有什麼關係,只是我們在使用時把它當做結構體的一個成員,通俗說,柔性陣列其實並不佔結構體的記憶體大小。
聯合體
聯合的宣告和結構類似,但它的行為方式卻和結構不同,聯合的所有成員引用的是記憶體中的相同位置。union聯合體大小取決於所有成員中,佔用空間最大的一個成員的大小。

union A
{
    double b;
    int i;
}A;
union B
{
    char a[9];
    int b;
}B;
union C
{
    char a[9];
    char c;
}C;
int main()
{
    printf("聯合體A:%d\n",sizeof(A));//8
    printf("聯合體B:%d\n",sizeof(B));//12
    printf("聯合體C:%d\n",sizeof(C));//9
    return 0;
}

在A中最大的成員是double型變數所以聯合體A大小為8;在B中int型別為最大,所以B的大小為4的整數倍,聯合體B佔用空間為12(最接近9的對界);在C中變數均為char型別,所以聯合體C的大小為9.
位段
位段的宣告和結構體類似,但它的成員是一個或多個位的欄位,這些不同長度的欄位實際上儲存於一個或多個整形變數中。
位段(struct)的記憶體對齊規則:
1.如果相鄰位域欄位的型別相同,且其位寬之和小於sizeof(type)的大小,則後面的欄位緊鄰前一個位元組儲存,直到 容納不下為止;基本成員是連續儲存的,若這個單元空間放不下下一個成員,則新開闢一個單元空間,這樣可以節 省記憶體空間。
2.如果相鄰位域欄位的型別相同,但其位寬之和大於sizeof(type)的大小,則後面的欄位將從新的單元開始,偏移量 為其型別大小的整數倍。
3.如果相鄰位域欄位的型別不相同,則各編譯器的實現有差異,vc6採取不壓縮方式,Dev-c++採取壓縮。
4.如果位域欄位之間穿插著非位域欄位,則不進行壓縮。
5.結構體的總大小為最大對齊數的整數倍。因為位斷成員必須宣告為int、signed int或unsigned int型別,因此結構體的 大小都是4的整數倍。

總結

#pragma pack(4)//預設對齊數為4
struct A
{
    char c1;  //1+3  char佔一個位元組,對齊到預設對齊數4的整倍數處
    int i1;   //4
    double d1;//8
}A;           //結構體A總大小:16
union B
{
    char c2;    //1
    int i2;    //4
    struct A a;//16
}B;            //聯合體B總大小:16
struct C
{
    unsigned int c3 : 4;  //4
    unsigned int c4 : 31; //4 位域
    unsigned int c5;      //4 非位域
    unsigned int c6 : 1;  //4
}C;                       //位段C總大小:16

struct D
{
    char c7;    //1+3
    double d2;  //8
    union B u;  //16
    struct C s; //16
    int i3;     //4
    struct E
    {
        int i4;  //4
        double d3;//8
    };                 //總和結構體D大小:60
}D;
int main()
{
    printf("結構體D:%d\n",sizeof(D));
    return 0;
}

這裡寫圖片描述