記憶體對齊的規則以及作用

2021-06-18 21:38:50 字數 4073 閱讀 4477

首先由乙個程式引入話題:1//

環境:vc6 + windows sp22//

程式13

#include 

<

iostream

>45

using

namespace

std;67

struct

st1 8;

1314

struct

st215;20

21int

main()

2227

程式的輸出結果為:

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的資料成員對齊結束,此時的記憶體狀態為:

o***|oooo|oo

0123 4567 89 (位址)

(x表示額外新增的位元組)

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

o***|oooo|ooxx

0123 4567 89ab  (位址)

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

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

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

1、  平台原因(移植原因):不是所有的硬體平台都能訪問任意位址上的任意資料的;某些硬體平台只能在某些位址處取某些特定型別的資料,否則丟擲硬體異常。

2、  效能原因:經過記憶體對齊後,cpu的記憶體訪問速度大大提公升。具體原因稍後解釋。

圖一:這是普通程式設計師心目中的記憶體印象,由乙個個的位元組組成,而cpu並不是這麼看待的。

圖二:cpu把記憶體當成是一塊一塊的,塊的大小可以是2,4,8,16位元組大小,因此cpu在讀取記憶體時是一塊一塊進行讀取的。塊大小成為memory access granularity(粒度)本人把它翻譯為「記憶體讀取粒度」 。【原文::memory access granularity,computer's processor  accesses memory in two-, four-, eight- 16- or even 32-byte chunks,不同cpu 粒度可能不一樣】

假設cpu要讀取乙個int型4位元組大小的資料到暫存器中,分兩種情況討論:

1、資料從0位元組開始

2、資料從1位元組開始

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

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

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

圖四:

此時cpu先訪問一次記憶體,讀取0—3位元組的資料進暫存器,並再次讀取4—5位元組的資料進暫存器,接著把0位元組和6,7,8位元組的資料剔除,最後合併1,2,3,4位元組的資料進暫存器。對乙個記憶體未對齊的資料進行了這麼多額外的操作,大大降低了cpu效能。

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

補充:什麼是對齊,以及為什麼要對齊:

現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定變數的時候經常在特定的記憶體位址訪問,這就需要各型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。 對齊的作用和原因:各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。其他平台可能沒有這種情況,但是最常見的是如果不按照適合其平台要求對資料存放進行對齊,會在訪問效率上帶來損失。比如有些平台每次讀都是從偶位址開始,如果乙個int型(假設為32位系統)如果存放在偶位址開始的地方,那麼乙個讀週期就可以讀出,而如果存放在奇位址開始的地方,就可能會需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該int資料。顯然在讀取效率上下降很多。這也是空間和時間的博弈。 對齊的實現 通常,我們寫程式的時候,不需要考慮對齊問題。編譯器會替我們選擇時候目標平台的對齊策略。當然,我們也可以通知給編譯器傳遞預編譯指令而改變對指定資料的對齊方法。 但是,正因為我們一般不需要關心這個問題,所以因為編輯器對資料存放做了對齊,而我們不了解的話,常常會對一些問題感到迷惑。最常見的就是struct資料結構的sizeof結果,出乎意料。為此,我們需要對對齊演算法所了解。

作用:指定結構體、聯合以及類成員的packing alignment;

語法:#pragma pack( [show] | [push | pop] [, identifier], n )

說明:1,pack提供資料宣告級別的控制,對定義不起作用;2,呼叫pack時不指定引數,n將被設成預設值;3,一旦改變資料型別的alignment,直接效果就是占用memory的減少,但是performance會下降;

語法具體分析:1,show:可選引數;顯示當前packing aligment的位元組數,以warning message的形式被顯示;2,push:可選引數;將當前指定的packing alignment數值進行壓棧操作,這裡的棧是the internal compiler stack,同時設定當前的packing alignment為n;如果n沒有指定,則將當前的packing alignment數值壓棧;3,pop:可選引數;從internal compiler stack中刪除最頂端的record;如果沒有指定n,則當前棧頂record即為新的packing alignment數值;如果指定了n,則n將成為新的packing aligment數值;如果指定了identifier,則internal compiler stack中的record都將被pop直到identifier被找到,然後pop出identitier,同時設定packing alignment數值為當前棧頂的record;如果指定的identifier並不存在於internal compiler stack,則pop操作被忽略;4,identifier:可選引數;當同push一起使用時,賦予當前被壓入棧中的record乙個名稱;當同pop一起使用時,從internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier沒有被找到,則忽略pop操作;5,n:可選引數;指定packing的數值,以位元組為單位;預設數值是8,合法的數值分別是1、2、4、8、16

記憶體對齊規則以及作用

首先由乙個程式引入話題 1 環境 vc6 windows sp22 程式13 include 45 using namespace std 6 7struct st1 8 13 14struct st2 15 20 21int main 22 27 程式的輸出結果為 sizeof st1 is 12...

記憶體對齊的規則以及作用

首先由乙個程式引入話題 include using namespace std struct st1 struct st2 int main cout 程式的輸出結果為 sizeof st1 is 12 sizeof st2 is 8 問題出來了,這兩個一樣的結構體,為什麼sizeof的時候大小不一...

記憶體對齊的規則以及作用

1 環境 vc6 windows sp2 2 程式1 3 include 4 5 using namespace std 6 7 struct st1 8 13 14 struct st2 15 20 21 int main 22 23 cout 程式的輸出結果為 sizeof st1 is 12 ...