關於內存對齊


內存地址對齊,是一種在計算機內存中排列數據(表現為變量的地址)、訪問數據(表現為CPU讀取數據)的一種方式,包含了兩種相互獨立又相互關聯的部分:基本數據對齊和結構體數據對齊 。

       為什么需要內存對齊?對齊有什么好處?是我們程序員來手動做內存對齊呢?還是編譯器在進行自動優化的時候完成這項工作?

       在現代計算機體系中,每次讀寫內存中數據,都是按字(word,4個字節,對於X86架構,系統是32位,數據總線和地址總線的寬度都是32位,所以最大的尋址空間為232 = 4GB(也 許有人會問,我的32位XP用不了4GB內存,關於這個不在本篇博文討論范圍),按A[31,30…2,1,0]這樣排列,但是請注意為了CPU每次讀寫 4個字節尋址,A[0]和A[1]兩位是不參與尋址計算的。)為一個塊(chunks)來操作(而對於X64則是8個字節為一個快)。注意,這里說的 CPU每次讀取的規則,並不是變量在內存中地址對齊規則。既然是這樣的,如果變量在內存中存儲的時候也按照這樣的對齊規則,就可以加快CPU讀寫內存的速 度,當然也就提高了整個程序的性能,並且性能提升是客觀,雖然當今的CPU的處理數據速度(是指邏輯運算等,不包括取址)遠比內存訪問的速度快,程序的執 行速度的瓶頸往往不是CPU的處理速度不夠,而是內存訪問的延遲,雖然當今CPU中加入了高速緩存用來掩蓋內存訪問的延遲,但是如果高密集的內存訪問,一 種延遲是無可避免的,內存地址對齊會給程序帶來了很大的性能提升。

       內存地址對齊是計算機語言自動進行的,也即是編譯器所做的工作。但這不意味着我們程序員不需要做任何事情,因為如果我們能夠遵循某些規則,可以讓編譯器做得更好,畢竟編譯器不是萬能的。

       為了更好理解上面的意思,這里給出一個示例。在32位系統中,假如一個int變量在內存中的地址是0x00ff42c3,因為int是占用4個字節,所以它的尾地址應該是0x00ff42c6,這個時候CPU為了讀取這個int變量的值,就需要先后讀取兩個word大小的塊,分別是0x00ff42c0~0x00ff42c3和0x00ff42c4~0x00ff42c7,然后通過移位等一系列的操作來得到,在這個計算的過程中還有可能引起一些總線數據錯誤的。但是如果編譯器對變量地址進行了對齊,比如放在0x00ff42c0,CPU就只需要一次就可以讀取到,這樣的話就加快讀取效率。

       1、基本數據對齊
                 在X86,32位系統下基於Microsoft、Borland和GNU的編譯器,有如下數據對齊規則:
                 a、一個char(占用1-byte)變量以1-byte對齊。
                 b、一個short(占用2-byte)變量以2-byte對齊。
                 c、一個int(占用4-byte)變量以4-byte對齊。
                 d、一個long(占用4-byte)變量以4-byte對齊。
                 e、一個float(占用4-byte)變量以4-byte對齊。
                 f、一個double(占用8-byte)變量以8-byte對齊。
                 g、一個long double(占用12-byte)變量以4-byte對齊。
                 h、任何pointer(占用4-byte)變量以4-byte對齊。

                而在64位系統下,與上面規則對比有如下不同:
                 a、一個long(占用8-byte)變量以8-byte對齊。
                 b、一個double(占用8-byte)變量以8-byte對齊。
                 c、一個long double(占用16-byte)變量以16-byte對齊。
                 d、任何pointer(占用8-byte)變量以8-byte對齊。

       2、結構體數據對齊
       結構體數據對齊,是指結構體內的各個數據對齊。在結構體中的第一個成員的首地址等於整個結構體的變量的首地址,而后的成員的地址隨着它聲明的順序和實際占用的字節數遞增。為了總的結構體大小對齊,會在結構體中插入一些沒有實際意思的字符來填充(padding)結構體。

       在結構體中,成員數據對齊滿足以下規則:
        a、結構體中的第一個成員的首地址也即是結構體變量的首地址。
        b、結構體中的每一個成員的首地址相對於結構體的首地址的偏移量(offset)是該成員數據類型大小的整數倍。
        c、結構體的總大小是對齊模數(對齊模數等於#pragma pack(n)所指定的n與結構體中最大數據類型的成員大小的最小值)的整數倍。

 

   7:  struct

   8:  {
   9:      char a;
  10:      int b;
  11:      short c;
  12:      char d;
  13:  }dataAlign;
  14:   
  15:  struct
  16:  {
  17:      char a;
  18:      char d;
  19:      short c;
  20:      int b;
  21:      
  22:  }dataAlign2;

       仔細觀察,會發現雖然是一樣的數據類型的成員,只不過聲明的順序不同,結構體占用的大小也不同,一個8-byte一個12-byte。為什么這樣,下面進行具體分析。  
       首先來看dataAlign2,第一個成員的地址等於結構體變量的首地址,第二個成員char類型,為了滿足規則b,它相對於結構體的首地址的偏移量必須 是char=1的倍數,由於前面也是char,故不需要在第一個和第一個成員之間填充,直接滿足條件。第三個成員short=2如果要滿足規則b,也不需 要填充,因為它的偏移量已經是2。同樣第四個也因為偏移量int=4,不需要填充,這樣結構體總共大小為8-byte。最后來驗證規則c,在VC中默認 的#pragma pack(n)中的n=8,而結構體中數據類型大小最大的為第四個成員int=4,故對齊模數為4,並且8 mode 4 = 0,所以滿足規則c。這樣整個結構體的總大小為8。

       對於dataAlign,第一個成員等於結構體變量首地址,偏移量為0,第二個成員為int=4,為了滿足規則b,需要在第一個成員之后填充3-byte,讓它相對於結構體首地址偏移量為4,結合運行結果,可知&dataAlign.a = 0x01109140,而&dataAlign.b = 0x01109144,它們之間相隔4-byte,0x01109141~0x01109143三個字節被0填 充。第三個成員short=2,無需填充滿足規則b。第四個成員char=1,也不需要填充。結構體總大小相加4 + 4 + 2 + 1 = 11。同樣最后需要驗證規則c,結構體中數據類型大小最大為第二個成員int=4,比VC默認對齊模數8小,故這個結構體的對齊模數仍然為4,顯然11 mode 4 != 0,故為了滿足規則c,需要在char后面填充一個字節,這樣結構體變量dataAlign的總大小為4 + 4 + 2 + 2 = 12。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM