1. 程式人生 > >初識-----記憶體對齊和位域

初識-----記憶體對齊和位域

記憶體對齊:

       首先,先看一段程式碼:

      

       我們可以看出上面那個結構體的長度為24,按照正常的計算方法,它的長度應該為14,但是現在卻是24,這是什麼原因呢?

       原來在程式執行的過程中為了便於cpu快速訪問,同時有效地節省儲存空間。c中編譯器會自動進行記憶體對齊。

那記憶體對齊具體的又是什麼呢?

        結構體資料記憶體對齊,是指結構體內的各個資料記憶體對齊。在結構體中的第一個成員的首地址等於整個結構體的變數的首地址,而後的成員的地址隨著它宣告的順序和實際佔用的位元組遞增。為了總的結構體大小對齊,會在結構體中插入一些沒有實際意思的字元來填充(padding)結構體。

        首先,每個特定平臺上的編譯器都有自己的預設“對齊係數”(也叫對齊模數,對齊模數等於#pragma pack(n)所指定的n與結構體中最大資料型別的成員大小的最小值)。我們可以通過預編譯命令#pragma

pack(n)  n=1,2,4,8,16來改變這一系數,其中的n就是要指定的“對齊係數”。

然後就是記憶體對齊要遵守的規則:

       在結構體中,成員資料對齊滿足以下規則:

              a、結構體中的第一個成員的首地址也即是結構體的首地址。

             b、結構體中的每一個成員在儲存時需要對齊到某個數字(對齊數)的整數倍處,即相對於結構體的首地址的偏移量(offset)應該對其到某個數字(對齊數)的整數倍處,那這個數字怎麼求得呢? 對齊數 = 編譯器預設對齊數的一個對齊數與該成員大小的較小值。

              c、結構體的總大小是結構體中最大對齊數(結構體的每個成員都有一個對齊數)的整數倍,不足的要補齊。

              d、如果一個結構體中嵌套了另一個結構體,巢狀的結構體(內部的結構體)對齊到自己的最大對齊數的整數倍處,結構體整體的大小就是所有最大對齊數的整數倍。

       比如上面的那個例子

       當儲存到 char  b 後,地址讀取要直接讀取到double c,就需要在b的後面補上7個空的位元組,使它成為一個長度為8位元組的型別,當最後一個char d儲存完後,它的長度就為 4 + 1 +3 + 8 + 1 = 17(紅色字型為補上去的記憶體,下同。) 又因為結構體的長度為結構體的總大小是結構體中最大對齊數的整數倍,不足的要補齊。,因此,應該給它補上3個位元組,使它達到24,所以後面輸出的結構體大小為24。

        再看一個例子:

        

       上面這兩段程式碼按理來說應該是一樣的,但是為什麼它們的大小不一樣呢?

       我們一起來分析一下:

       看第一段程式碼: 按照我們的分析,它的大小應該為 1 + 3  + 4 + 16 = 24,這是因為當我們計算B的大小時,首先計算的是前面兩個基本型別,他們之間根據記憶體對齊應該給第一個大小加3再加上第二個的大小所以應該為 1 + 3  + 4  = 8,然後加由結構體A建立的變數,我們可以看到,A結構體大小為16,A的最大對齊數為8,而前面存完偏移值剛好到8,因此,直接用前面的加後面的A所以答案為8 +  16 = 24。又因為24為A,B 中總最大對齊數的8的整數倍,所以B的大小為24。

       看第二段程式碼: 按照我們的分析,它的大小應該為 1 + 3  + 4 + 24 = 32,這是因為當我們計算B的大小時,首先計算的是前面兩個基本型別,他們之間根據記憶體對齊應該給第一個大小加3再加上第二個的大小所以應該為 1 + 3  + 4  = 8,然後加由結構體A建立的變數的大小,我們可以看到,A的大小為 1 + 1 + 6 + 8 + 1 + 7 = 24 ,因此B的大小為8 + 24 = 32。又因為32為A,B 中對齊模數最大的8的整數倍,所以B的大小為32。

        所以我們可以發現,結構體定義時,成員的定義先後順序不同,結構體大小可能也不同,因此在定義結構體時,應注意定義的順序。

        以上就是我個人關於記憶體對齊的一些理解;

位域:

         首先,介紹位域:

        位域是指資訊在儲存時,並不需要佔用一個完整的位元組, 而只需佔幾個或一個二進位制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進   位即可。為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾 個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。

        位域定義:   與結構定義相仿,其形式為:
        struct 位域結構名
        { 位域列表 };
        其中位域列表的形式為: 型別說明符 位域名:位域長度
        例如:
        struct A
        {

           int a:8;

           int b:2;

           int c:6;

         };

         1. 寬度為 0 的一個未命名位域強制下一位域對齊到其下一type邊界,其中type是該成員的型別。

             struct bs

             {
                unsigned a:4;
                unsigned :0 ;/*空域*/
                char b:4 ;/*從下一單元開始存放*/
                unsigned c:4;
             }B;

             實驗中,0x0012ff74為變數B的起始地址,位域a填充0x0012ff74的後四位,位域b從0x0012ff78開始,佔據0x0012ff78的後四位。所以空域佔據了從a開始的4個位剩餘部 分。乍看 VC6對空域的處理是依據空域的型別,即unsigned。其實不然。經試驗,空域所佔大小和 a的型別及 空域的型別 二者皆相關。

即以下四種情況:

                           1.  a,空域皆為char時,二者共佔據1位元組

                           2.  a 為unsigned,空域為unsigned; 

                           3.  a 為char,空域為unsigned;

                           4.  a 為unsigned,空域為char;

                           後三種情況,二者共佔據4位元組。

         2. 位域的長度不能大於指定型別固有長度,比如說int的位域長度不能超過32,char 的位域長度不能超過8。

         3. 位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:

            struct A

           {

               int a:1

               int :2 /*該2位不能使用*/

               int b:3

               int c:2

            };

            從以上分析可以看出,位域在本質上就是一種結構型別, 不過其成員是按二進位分配的。

同樣看一段程式碼:

由程式碼我們可以看出,位域的作用,A的大小計算應該為: 4 + 1+ 1 +2 = 8;

如果當前位元組位數不足,那麼編譯器會將下一個變數單獨放置,比如上面的程式碼中的char c ,雖然運用了位域,但是一個char 是8位而 c佔了兩位,因此,d佔了一塊新的記憶體。

位域只有資料型別相同才能合併,比如b和c 因為資料型別不同,儘管int的位數夠了,但是仍不能合併。

以上就是本人理解的位域內容。