内存对齐详解
為什么要進(jìn)行內(nèi)存對其?
(1)?性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。
(2) 平臺原因:不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
(3)?空間原因:沒有進(jìn)行內(nèi)存對齊的結(jié)構(gòu)體或類會(huì)浪費(fèi)一定的空間,當(dāng)創(chuàng)建對象越多時(shí),消耗的空間越多。
?
在 C/C++ 中,結(jié)構(gòu)體/類是一種復(fù)合數(shù)據(jù)類型,其構(gòu)成元素既可以是基本數(shù)據(jù)類型(如int、long、float等)的變量,也可以是一些復(fù)合數(shù)據(jù)類型(如數(shù)組、結(jié)構(gòu)、聯(lián)合等)的數(shù)據(jù)單元。編譯器為每個(gè)成員按其自然邊界(alignment)分配空間。各個(gè)成員按照它們被聲明的順序在內(nèi)存中順序存儲,第一個(gè)成員的地址和整個(gè)結(jié)構(gòu)的地址相同。
如果一個(gè)變量的內(nèi)存地址正好位于它長度的整數(shù)倍,他就被稱做自然對齊。如果在 32 位的機(jī)器下,一個(gè)int類型的地址為0x00000004,那么它就是自然對齊的。同理,short 類型的地址為0x00000002,那么它就是自然對齊的。char 類型就比較 "隨意" 了,因?yàn)樗旧黹L度就是 1 個(gè)字節(jié)。自然對其的前提下:
char?? 偏移量為sizeof(char) 即 1 的倍數(shù)? short??偏移量為sizeof(short) 即 2 的倍數(shù)? int?? 偏移量為sizeof(int) 即 4 的倍數(shù)? float??偏移量為sizeof(float) 即 4 的倍數(shù)? double?偏移量為sizeof(double) 即 8 的倍數(shù)??
在設(shè)置結(jié)構(gòu)體或類時(shí),不考慮內(nèi)存對齊問題,會(huì)浪費(fèi)一些空間,例如實(shí)驗(yàn)一:
struct asd1{char a;int b;short c; };//12字節(jié)struct asd2{char a;short b;int c; };//8字節(jié)上面兩個(gè)結(jié)構(gòu)體擁有相同的數(shù)據(jù)成員 char、short 和 int,但由于各個(gè)成員按照它們被聲明的順序在內(nèi)存中順序存儲,所以不同的聲明順序?qū)е铝私Y(jié)構(gòu)體所占空間的不同。具體如下圖:
? ? ? ? ? ? ?
?
看到上面的第二張圖,有的人可能會(huì)有疑問,為什么 short 不是緊挨著 char 呢?其實(shí)這個(gè)原因在上面已經(jīng)給出了答案——自然對齊。為此,我們可以創(chuàng)建結(jié)構(gòu)體驗(yàn)證自然對齊的規(guī)則。實(shí)驗(yàn)很簡單,在原本 short 類型變量前后添加 char 類型,看結(jié)果是怎樣的。實(shí)驗(yàn)二:
struct asd3{char a;char b;short c;int d; };//8字節(jié)struct asd4{char a;short b;char cint d; };//12字節(jié)? ? ? ? ? ? ??
?
需要注意的一點(diǎn)
當(dāng)數(shù)據(jù)成員中有 double 和 long 時(shí),情況又會(huì)有一點(diǎn)變化。還是以上面的結(jié)構(gòu)體 asd1 和 asd2 為基礎(chǔ),都添加 double 型數(shù)據(jù)成員。來看看結(jié)果是什么,實(shí)驗(yàn)三:
struct asd1{char a;int b;short c;double d; };//24個(gè)字節(jié)struct asd2{char a;short b;int c;double d; };//16個(gè)字節(jié)只添加了一個(gè) double,但 struct asd1 的大小從 12 變到了 24。而 struct asd2 的大小從 8 變到了 16。不需要迷惑,因?yàn)檫@和 double 的自然對其有關(guān)(需要注意)。原本的 asd1 占 12 個(gè)字節(jié)大小,但是 double 對齊需要是 8 的倍數(shù),所以在 short 后面又填充了 4 個(gè)字節(jié)。此時(shí),asd1 的占 16 個(gè)字節(jié),再加上 double 的 8 個(gè)字節(jié)就成了 24 個(gè)字節(jié)。而 asd2 沒有這個(gè)問題,它原本占 8 個(gè)字節(jié)。因?yàn)檎媚軐R,所以添加 double 后占 16 個(gè)字節(jié)。具體情況如下圖所示:?
? ? ? ? ? ? ? ??? ? ?
?
?
指定對齊值
在缺省情況下,C 編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變?nèi)笔〉膶鐥l件:
使用偽指令 #pragma pack (n),C 編譯器將按照 n 個(gè)字節(jié)對齊。
使用偽指令 #pragma pack (),取消自定義字節(jié)對齊方式。
實(shí)驗(yàn)四:
#pragma pack(4) struct asd5{char a;int b;short c;float d;char e; };//20 #pragma pack()#pragma pack(1) struct asd6{char a;int b;short c;float d;char e; };//12 #pragma pack()使用 #pragma pack (value)?指令將結(jié)構(gòu)體按相應(yīng)的值進(jìn)行對齊。兩個(gè)結(jié)構(gòu)體包含同樣的成員,但是卻相差 8 個(gè)字節(jié)。難道我們只需要通過簡單的指令就能完成內(nèi)存對齊的工作嗎?其實(shí)不是的。上面的對齊結(jié)果如下:
以 32 位機(jī)器為例,CPU 取的字長是 32 位。所以上面的對齊結(jié)果會(huì)這樣帶來的問題是:訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問。如果我要獲取 int 和 float 的數(shù)據(jù),處理器需要訪問兩次內(nèi)存,一次獲取 "前一部分" 的值,一次獲取 "后一部分" 的值。這樣做雖然減少了空間,但是增加訪問時(shí)間的消耗。其實(shí)最理想的對齊結(jié)果應(yīng)該是:
ps.使用 #pragma pack(4) 可以讓前面的實(shí)驗(yàn)三中的 asd1 少占用 4 字節(jié)。
?
?
對齊原則
1. 數(shù)據(jù)類型自身的對齊值:對于 char 型數(shù)據(jù),其自身對齊值為1,對于 short 型為2,對于 int,float,double 類型,其自身對齊值為 4,單位字節(jié)。(上面實(shí)驗(yàn)二已經(jīng)驗(yàn)證過了)
2.?結(jié)構(gòu)體或者類的自身對齊值:其成員中自身對齊值最大的那個(gè)值。
3. 指定對齊值:#pragma pack (value) 時(shí)的指定對齊值 value。
4.?數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對齊值:自身對齊值和指定對齊值中小的那個(gè)值。
總結(jié)
- 上一篇: mmap 内存映射详解
- 下一篇: 快用一用 lambda 表达式吧,让你的