天天看點

C/C++記憶體對齊規則及作用

 首先由一個程式引入話題:

//環境:vc6 + windows sp2
//程式1
#include <iostream>

using namespace std;

struct st1 
{
    char a ;
    int  b ;
    short c ;
};

struct st2
{
    short c ;
    char  a ;
    int   b ;
};

int main()
{
    cout<<"sizeof(st1) is "<<sizeof(st1)<<endl;
    cout<<"sizeof(st2) is "<<sizeof(st2)<<endl;
    return 0 ;
}
           

程式的輸出結果為:

 sizeof(st1) is 12

 sizeof(st2) is 8 

問題出來了,這兩個一樣的結構體,為什麼sizeof的時候大小不一樣呢?

本文的主要目的就是解釋明白這一問題。

記憶體對齊,正是因為記憶體對齊的影響,導緻結果不同。

對于大多數的程式員來說,記憶體對齊基本上是透明的,這是編譯器該幹的活,編譯器為程式中的每個資料單元安排在合适的位置上,進而導緻了相同的變量,不同聲明順序的結構體大小的不同。

       那麼編譯器為什麼要進行記憶體對齊呢?程式1中結構體按常理來了解sizeof(st1)和sizeof(st2)結果都應該是7,4(int) + 2(short) + 1(char) = 7 。經過記憶體對齊後,結構體的空間反而增大了。

在解釋記憶體對齊的作用前,先來看下記憶體對齊的規則:

1、  對于結構的各個成員,第一個成員位于偏移為0的位置,以後每個資料成員的偏移量必須是min(#pragma pack()指定的數,這個資料成員的自身長度) 的倍數。

2、  在資料成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊将按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行。

#pragma pack(n) 表示設定為n位元組對齊。 VC6預設8位元組對齊

以程式1為例解釋對齊的規則 :

St1 :char占一個位元組,起始偏移為0 ,int 占4個位元組,min(#pragma pack()指定的數,這個資料成員的自身長度) = 4(VC6預設8位元組對齊),是以int按4位元組對齊,起始偏移必須為4的倍數,是以起始偏移為4,在char後編譯器會添加3個位元組的額外位元組,不存放任意資料。short占2個位元組,按2位元組對齊,起始偏移為8,正好是2的倍數,無須添加額外位元組。到此規則1的資料成員對齊結束,此時的記憶體狀态為:

C/C++記憶體對齊規則及作用

共占10個位元組。還要繼續進行結構本身的對齊,對齊将按照#pragma pack指定的數值和結構(或聯合)最大資料成員長度中,比較小的那個進行,st1結構中最大資料成員長度為int,占4位元組,而預設的#pragma pack 指定的值為8,是以結果本身按照4位元組對齊,結構總大小必須為4的倍數,需添加2個額外位元組使結構的總大小為12 。此時的記憶體狀态為:

C/C++記憶體對齊規則及作用

到此記憶體對齊結束。St1占用了12個位元組而非7個位元組。

St2 的對齊方法和st1相同,讀者可自己完成。

記憶體對齊的主要作用是:

1、  平台原因(移植原因):不是所有的硬體平台都能通路任意位址上的任意資料的;某些硬體平台隻能在某些位址處取某些特定類型的資料,否則抛出硬體異常。

2、  性能原因:經過記憶體對齊後,CPU的記憶體通路速度大大提升。具體原因稍後解釋。

圖一:

C/C++記憶體對齊規則及作用

這是普通程式員心目中的記憶體印象,由一個個的位元組組成,而CPU并不是這麼看待的。

圖二:

C/C++記憶體對齊規則及作用

CPU把記憶體當成是一塊一塊的,塊的大小可以是2,4,8,16位元組大小,是以CPU在讀取記憶體時是一塊一塊進行讀取的。塊大小成為memory access granularity(粒度) 本人把它翻譯為“記憶體讀取粒度” 。

假設CPU要讀取一個int型4位元組大小的資料到寄存器中,分兩種情況讨論:

1、資料從0位元組開始

2、資料從1位元組開始

再次假設記憶體讀取粒度為4。

圖三:

C/C++記憶體對齊規則及作用

當該資料是從0位元組開始時,很CPU隻需讀取記憶體一次即可把這4位元組的資料完全讀取到寄存器中。

    當該資料是從1位元組開始時,問題變的有些複雜,此時該int型資料不是位于記憶體讀取邊界上,這就是一類記憶體未對齊的資料。

圖四:

C/C++記憶體對齊規則及作用

此時CPU先通路一次記憶體,讀取0—3位元組的資料進寄存器,并再次讀取4—5位元組的資料進寄存器,接着把0位元組和6,7,8位元組的資料剔除,最後合并1,2,3,4位元組的資料進寄存器。對一個記憶體未對齊的資料進行了這麼多額外的操作,大大降低了CPU性能。

    這還屬于樂觀情況了,上文提到記憶體對齊的作用之一為平台的移植原因,因為以上操作隻有有部分CPU肯幹,其他一部分CPU遇到未對齊邊界就直接罷工了。

圖檔來自:Data alignment: Straighten up and fly right 

如大家對記憶體對齊對性能的具體影響情況,可以參考上文。

繼續閱讀