sizeof運算子的記憶體對齊問題

2021-07-04 22:26:56 字數 4375 閱讀 5828

一、什麼是位元組對齊

計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定型別變數的時候經常在特定的記憶體位址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排放,這就是對齊。

二、對齊的作用和原因:

1、平台原因(移植原因):不是所有的硬體平台都能訪問任意位址上的任意資料的;某些硬體平台只能在某些位址處取某些特定型別的資料,否則丟擲硬體異常。各個硬體平台對儲存空間的處理上有很大的不同。一些平台對某些特定型別的資料只能從某些特定位址開始訪問。比如有些架構的cpu在訪問乙個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊。

2、效能原因

:最常見的是如果不按照適合其平台要求對資料存放進行對齊,會在訪問效率上帶來損失。

資料結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問,而對齊的記憶體訪問僅需要一次訪問。比如有些平台每次讀都是從偶位址開始,如果乙個int型(假設為32位系統)如果存放在偶位址開始的地方,那麼乙個讀週期就可以讀出這32bit,而如果存放在奇位址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit資料。顯然在讀取效率上下降很多。

三、對齊規則

每個特定平台上的編譯器都有自己的預設「對齊係數」(也叫對齊模數)。程式設計師可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16 來改變這一係數,其中的n就是你要指定的「對齊係數」。

規則:

1、資料成員對齊規則:結構(struct)(或聯合(union))的資料成員,第乙個資料成員放在offset為0的地方,以後每個資料成員的對齊 按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。

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

3、當#pragma pack的n值等於或超過所有資料成員長度的時候,這個n值的大小將不產生任何效果。

四、請看下面的結構:

struct mystruct;

為上面的結構分配空間的時候,vc根據成員變數出現的順序和對齊方式,先為第乙個成員dda1分配空間,其起始位址跟結構的起始位址相同(剛好偏移量0剛好為sizeof(double)的倍數),該成員變數占用sizeof(double)=8個位元組;接下來為第二個成員dda分配空間,這時下乙個可以分配的位址對於結構的起始位址的偏移量為8,是sizeof(char)的倍數,所以把dda存放在偏移量為8的地方滿足對齊方式,該成員變數占用 sizeof(char)=1個位元組;接下來為第三個成員type分配空間,這時下乙個可以分配的位址對於結構的起始位址的偏移量為9,不是sizeof (int)=4的倍數,為了滿足對齊方式對偏移量的約束問題,vc自動填充3個位元組(這三個位元組沒有放什麼東西),這時下乙個可以分配的位址對於結構的起始位址的偏移量為12,剛好是sizeof(int)=4的倍數,所以把type存放在偏移量為12的地方,該成員變數占用sizeof(int)=4個位元組;這時整個結構的成員變數已經都分配了空間,總的占用的空間大小為:8+1+(3)+4=16,剛好為結構的位元組邊界數(即結構中占用最大空間的型別所占用的位元組sizeof(double)=8)的倍數,所以沒有空缺的位元組需要填充。所以整個結構的大小為:sizeof(mystruct)=8+1+ (3)+4=16,其中有3個位元組是vc自動填充的,沒有放任何有意義的東西。

下面再舉個例子,交換一下上面的mystruct的成員變數的位置,使它變成下面的情況:

struct mystruct;

這個結構占用的空間為多大呢?在vc6.0環境下,可以得到sizeof(mystruc)為24。結合上面提到的分配空間的一些原則,分析下vc怎麼樣為上面的結構分配空間的。

struct mystruct;

所以該結構總的大小為:sizeof(mystruc)為1+(7)+8+4+(4)=24。其中總的有7+4=11個位元組是vc自動填充的,沒有放任何有意義的東西。

vc對結構的儲存的特殊處理確實提高cpu儲存變數的速度,但是有時候也帶來了一些麻煩,我們也遮蔽掉變數預設的對齊方式,自己可以設定變數的對齊方式。vc 中提供了#pragma pack(n)來設定變數以n位元組對齊方式。n位元組對齊就是說變數存放的起始位址的偏移量有兩種情況:第

一、如果n大於等於該變數所占用的位元組數,那麼偏移量必須滿足預設的對齊方式,第

二、如果n小於該變數的型別所占用的位元組數,那麼偏移量為n的倍數,不用滿足預設的對齊方式。結構的總大小也有個約束條件,分下面兩種情況:如果n大於所有成員變數型別所占用的位元組數,那麼結構的總大小必須為占用空間最大的變數占用的空間數的倍數;否則必須為n的倍數。

下面舉例說明其用法。

#pragma pack(push) 

#pragma pack(4)

struct test;

#pragma pack(pop)

以上結構的大小為16,下面分析其儲存情況,首先為m1分配空間,其偏移量為0,滿足我們自己設定的對齊方式(4位元組對齊),m1占用1個位元組。接著開始為 m4分配空間,這時其偏移量為1,需要補足3個位元組,這樣使偏移量滿足為n=4的倍數(因為sizeof(double)大於n),m4占用8個位元組。接著為m3分配空間,這時其偏移量為12,滿足為4的倍數,m3占用4個位元組。這時已經為所有成員變數分配了空間,共分配了16個位元組,滿足為n的倍數。如果把上面的#pragma pack(4)改為#pragma pack(16),那麼我們可以得到結構的大小為24。

五、再看下面這個例子

#pragma pack(8)

struct s1;

struct s2;

#pragma pack(pop)

sizeof(s2)結果為24.。成員對齊有乙個重要的條件,即每個成員分別對齊。即每個成員按自己的方式對齊。也就是說上面雖然指定了按8位元組對齊,但並不是所有的成員都是以8位元組對齊。其對齊的規則是,每個成員按其型別的對齊引數(通常是這個型別的大小)和指定對齊引數(這裡是8位元組)中較小的乙個對齊。並且結構的長度必須為所用過的所有對齊引數的整數倍,不夠就補空位元組。

s1中,成員a是1位元組預設按1位元組對齊,指定對齊引數為8,這兩個值中取1,a按1位元組對齊;成員b是4個位元組,預設是按4位元組對齊,這時就按4位元組對齊,所以sizeof(s1)應該為8;

s2 中,c和s1中的a一樣,按1位元組對齊,而d 是個結構,它是8個位元組,它按什麼對齊呢?對於結構來說,它的預設對齊方式就是它的所有成員使用的對齊引數中最大的乙個,s1的就是4.所以,成員d就是按4位元組對齊。成員e是8個位元組,它是預設按8位元組對齊,和指定的一樣,所以它對到8位元組的邊界上,這時,已經使用了12個位元組了,所以又新增了4個位元組的空,從第16個位元組開始放置成員e.這時,長度為24,已經可以被8(成員e按8位元組對齊)整除。這樣,一共使用了24個位元組。

s1的記憶體布局:11**,1111,

s2的記憶體布局:1***,11**,1111,****11111111

這裡有三點很重要:

1.每個成員分別按自己的方式對齊,並能最小化長度。

2.複雜型別(如結構)的預設對齊方式是它最長的成員的對齊方式,這樣在成員是複雜型別時,可以最小化長度。

3.對齊後的長度必須是成員中最大的對齊引數的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊。

六、sizeof例子(注意:下面的例子都是經過測試驗證的)

std::cout <<"void* size"<

七、其它 

1、在編寫**時候可以通過#pragma pack(n),n=1,2,4,8,16來靈活控制記憶體對齊的係數,當需要關閉記憶體對齊時,可以使用#pragma pack()實現。

2、注意事項

記憶體對齊可以大大的提高編譯器的處理速度,但不是任何時候都是必需的,有的時候不注意的話,還可能出現意想不到的錯誤!最典 型的情況就是網路通訊程式的編碼中,

一定要在定義結構體或者聯合之前使用#pragma pack()把記憶體對齊關閉,這是因為遠端主機通 常不知道對方使用的何種對齊方式,通過socket接收的位元組流,然後按照位元組解析

得到對應的結果,如果使用記憶體對齊,遠端主機很 可能會得到錯誤的結果!這種情況曾經指導上機時遇到過,而且屬於比較隱蔽的錯誤,debug了好久才發現問題出在這裡。

3、優化結構體

雖然記憶體對齊可以提高執行效率,但是卻浪費了記憶體,在現代pc上通常不會在乎這點小的空間,但是在一些記憶體很小的嵌入式裝置上,可能就要錙銖必較了。其實我們發現在不影響功能的前提下,可以調整成員的順序來減少「記憶體空洞」帶來的浪費。

(24)位運算子 sizeof運算子

位運算子作用於整數型別,並把運算物件看成是二進位制的集合。位運算子提供檢查和設定二進位制位的功能,滿足左結合律。但是位運算關於符號位如何處理沒有明確的規定,改變符號位的值是一種未定義的行為,所以建議僅將位運算子作用於無符號 型。左移運算子 右移運算子 的行為則依賴於其左側運算物件物件的型別 如果該運...

sizeof運算子詳解

1 sizeof應用在結構上的情況 請看下面的結構 struct mystruct 對結構mystruct採用sizeof會出現什麼結果呢?sizeof mystruct 為多少呢?也許你會這樣求 sizeof mystruct sizeof double sizeof char sizeof in...

關於sizeof運算子

1 sizeof運算子 sizeof是乙個特殊的運算子,它有兩種形式 sizeof 表示式和sizeof 型別名 對於sizeof運算子要注意幾點 1 如果是表示式的話,括號可以省略,但是對於型別,括號不能省 2 sizeof求算的是所佔的空間,如果作用於表示式,這個表示式是不進行求值的,只根據型別...