如何理解 struct 的記憶體對齊?

時間 2021-05-08 16:54:02

1樓:獨孤九劍

定義乙個巨集求結構體內的成員的偏移值:

#define offset(type, member) \(size_t)&(((type*)0)->member)解釋:首先 (type*)0 定乙個type型別例項開始位址是0,((type*)0)->member 獲取(type*)0的內部成員變數member

&(((type*)0)->member) 獲取內部成員變數的位址(size_t)&(((type*)0)->member) 將位址強制轉換成 int資料型別

#include

struct father ;

#define offset(type, member) \(size_t)&(((type*)0)->member)int main()

輸出:4

2樓:

變數作為資料儲存在記憶體,CPU負責計算,在計算的時候需要從記憶體讀取資料,定址讀取資料以4位元組進行讀取,對應暫存器尺寸。定址讀取資料的過程從4位元組倍數記憶體位址開始讀取讀4位元組,為了方便CPU定址盡量一次就能對資料讀取完成,所以要將變數按照這個規則儲存。變數型別中 short int long 這些型別任意的組合都是可以放在恰好4位元組記憶體段的,導致需要對齊的是型別char 占用1位元組,就需要將char儲存在乙個合理的地方避免有變數儲存的時候跨 4位元組記憶體段,所以編譯器會對struct中的屬性變數的儲存尺寸做調整。

寫的不嚴謹,只是個大概的形象認知,若有理解錯誤的地方望路過的大佬幫忙指正,感謝~

3樓:KDF5000

位元組對齊主要是為了提高記憶體的訪問效率,比如intel 32位cpu,每個匯流排週期都是從偶位址開始讀取32位的記憶體資料,如果資料存放位址不是從偶數開始,則可能出現需要兩個匯流排週期才能讀取到想要的資料,因此需要在記憶體中存放資料時進行對齊。

通常我們說位元組對齊很多時候都是說struct結構體的記憶體對齊,比如下面的結構體:

struct A

在32位機器上char 佔1個位元組,int 佔4個位元組,short佔2個位元組,一共占用7個位元組.但是實際真的是這樣嗎?

我們先看下面程式的輸出:

#include

struct A;

int main()

測試輸出的結果是A: 12, 比計算的7多了5個位元組。這個就是因為編譯器在編譯的時候進行了記憶體對齊導致的。

記憶體對齊主要遵循下面三個原則:

結構體變數的起始位址能夠被其最寬的成員大小整除

結構體每個成員相對於起始位址的偏移能夠被其自身大小整除,如果不能則在前乙個成員後面補充位元組

結構體總體大小能夠被最寬的成員的大小整除,如不能則在後面補充位元組

其實這裡有點不嚴謹,編譯器在編譯的時候是可以指定對齊大小的,實際使用的有效對齊其實是取指定大小和自身大小的最小值,一般預設的對齊大小是4。

再回到上面的例子,如果預設的對齊大小是4,結構體a的起始位址為0x0000,能夠被最寬的資料成員大小(這裡是int, 大小為4,有效對齊大小也是4)整除,姑char a的從0x0000開始存放占用乙個位元組即0x0000~0x0001,然後是int b,其大小為4,故要滿足2,需要從0x0004開始,所以在char a後填充三個位元組,因此a對齊後占用的空間是0x0000~0x0003,b占用的空間是0x0004~0x0007, 然後是short c其大小是2,故從0x0008開始占用兩個位元組,即0x0008~0x0009。 此時整個結構體占用的空間是0x0000~0x0009, 占用10個位元組,10%4 != 0, 不滿足第三個原則,所以需要在後面補充兩個位元組,即最後記憶體對齊後占用的空間是0x0000~0x000B,一共12個位元組。

4樓:

CPU是按字讀取記憶體。所以記憶體對齊的話,不會出現某個型別的資料讀一半的情況,需要再二次讀取記憶體。可以提公升訪問效率。

Reference: Purpose of memory alignment

5樓:

背書式:各成員變數存放的起始位址相對於結構的起始位址的偏移量必須為該變數的型別所占用的位元組數的倍數各成員變數在存放的時候根據在結構中出現的順序依次申請空間同時按照上面的對齊方式調整位置空缺的位元組自動填充同時為了確保結構的大小為結構的位元組邊界數(即該結構中占用最大的空間的型別的位元組數)的倍數,所以在為最後乙個成員變數申請空間後還會根據需要自動填充空缺的位元組

多!簡!單!

6樓:xjsxjtu

我認為歸根結底是編譯器想通過空間換時間,通過適當增加padding,使每個成員的訪問都在乙個指令裡完成,而不需要兩次訪問再拼接。

理解有誤的話請無視…

7樓:張道遠

padding = (align - (offset mod align)) mod align

new offset = offset + padding = offset + (align - (offset mod align)) mod align

the total size of the structure should be a multiple of the largest alignment of any structure memberwikipedia

生活很艱難

世界多姿多彩,世上有各種不同的人存在

但我們還是要在一起呀在一起

乙個最小儲存單位為 8 位元組的記憶體來說。訪問位址1, 大小為 4 位元組的資料。只需讀取位址 0 的 8 位元組的資料。

然後在出口處移位下就行。因為只需在出口處做一次處理,所以可以不計成本進行移位優化,但這種優化只能在部件內部或者部件組內部進行。不同元件的互動部分對「對齊」還是有不同的看法的。

又因為 RISC CPU 的設計,大多精簡指令集的指令長就是字長。而指令還需區分取立即數和各種 action, 一字長的指令無法全部用來表示位址空間。綜上,大多 RISC CPU 強制位址對齊,位址的低位腦補成 0。

順便也減少了位址線的寬度。

訪問記憶體的速度是非常非常非常……慢的。再加上 CPU 及其指令設計的限制。在這種艱苦條件下,我們必須無所不用其極地減少隨機記憶體訪問次數:

在乙個訪問週期裡讀寫最多,但不更多的資料。也就是一字長大小的資料。

對於 CPU, 記憶體的最小儲存單元的大小為最大,但不更大的 CPU 字長。

基於此,一般的 RISC CPU 的位址線寬度為 . 比如乙個 32 位 CPU 的字長是 32bit, 位元組大小為 8bit, 那麼位址線寬度為 可選擇 個位址單位, 每個單位的大小是 4 位元組於是總共可管理 位元組的記憶體。這也是邏輯位址最低 位總是為 0 的來歷了。

對於 32bit 字長的 CPU, 就是最低 2 位為 0 了。注意,此段所述只是基於 de-facto 習慣(1 byte = 8 bit, mem 空間大小為

此時,如果我們需要訪問位址為 2 的大小為 1 字長也就是 4 位元組的資料,也就是 2-5 的資料. 位址 2 mod 大小 4 = 2 不為 0. 這是未對齊的訪問。

(位址線以二進位制表示。最後兩位空置,所以始終為邏輯 0)我們需要將位址線設為 0(00) 讀出 0-3, 取 2-3, 然後將位址線設為 1(00) 讀出 4-7 取 4-5, 然後再合成所需資料。共兩次訪問。

如果要訪問位址為 0(00) 或者 1(00) 的大小為 1 字長的資料。則一次訪問即可。

這就是位址不對齊導致訪問變慢的來歷了。

此時若要訪問位址 2 的半個字長大小的資料。也就是 2-3 的資料。我們可以將位址設為 0(00) 讀出 0-3 的資料,然後將其在暫存器中右移 2 位元組即可。

那麼,問題來了。既然如此,我也可以訪問位址 1, 大小為 2 位元組的資料啊。也就是 1-2 的資料。

將位址線設成 0(00) 讀出資料,然後在暫存器內左移 1 位元組,再右移 2 位元組就行了啊。

這時候 1 mod 2= 1, 不為 0, 但需要訪問記憶體的次數還是一次。

但世界上有許多地方,那兒的 CPU 字長只有 2 位元組。當資料到那些地方去旅行時。那兒的 CPU 訪問位址為 1, 大小為 2 位元組的資料的時候還是需要兩次的。

那麼,問題又來了。字長為 4 位元組的 CPU, 訪問 8 位元組長度的資料,這個資料反正始終都要讀兩次,那麼它不對齊也是可以的呀。只要他的位址 mod 4 為 0 就可以了。

但世界上還有些地方,那兒的 CPU 字長是 8 位元組的,當資料到那些地方去旅行時。那兒的 CPU 訪問大小為 8 位元組的資料,若其位址 mod 8 為 0 時,只要讀一次就夠了。此時讀兩次就是一種浪費了。

大小為 size 的字段,他的結構內偏移 offset 需符合 offset mod size 為 0.引用的 wikipedia 的第一段就是對這句話的精確表述。

最後,問題又來了。

struct hi

together 欄位的 padding 是要多少?是的 padding 0 就行了。所以大小是 8 + 8 + 2 + 2= 20

那為什麼 gcc 告訴我們應該是 24 呢。

因為我們的世界不是孤單的世界。

資料們可以歡樂地組成團隊。

hi group[2];

如果我們不能相互體諒,自私地將最後的 padding 設為 0 的話。

假設第乙個 hi 位於位址 0, 那麼第二個 hi 就得從位址 20 開始了。此時 us 的位址是

, 而 28 mod 8= 4 不為 0.

如果 hi 的大小為其中最大單元的整數倍也就是 8 * 3= 24 的話。那麼第二個 hi 的 us 欄位的位址是 24+8= 32. 而 32 mod 8= 0, 對齊了。

所以,最後我們還需要 padding 4 位元組。

在這個不孤單的世界裡,為了同一類資料能和諧相處。所以我們鄭重做出約定:

整個結構的大小必須是其中最大字段大小的整數倍。

於是,不管是在乙個陣列裡沒羞沒臊地在一起。還是在這個如此多姿多彩各不相同的世界裡到處旅行。資料們的美好的生活都可以快速,和諧,一致地進行啦。

最後,若題主有閒,推薦看一下哈佛的 CS101, From NAND to Tetris 課程。從最簡單的邏輯門開始,自己動手打造一遍 latch, flip-flop, register,RAM, ALU, CPU, assembler, compiler. 相信到時候你會有更深的體會。

如何評價vivo Origin OS的 記憶體融合技術?

Tant swap技術,類似虛擬記憶體,以前emmc太慢大家都關掉,現在UFS3.1和SFS1.0流行了,swap技術改進了,又用起來了,僅此而已。 alwynlee 完全不懂這些,我就想知道會不會增加耗電量,12G運存都用不完,如果會增加耗電量那我肯定要關了。如果不的話,就無所謂了。 三木 個人覺...

怎樣通俗的理解作業系統中記憶體管理分頁和分段?

Bowen Xiao 感覺高讚說的有些問題,結合點我自己的想法。分段講的有點問題。照這個答案,分段解決的是虛擬記憶體解決的問題,但實際上兩者不能劃等號。應該說現代作業系統的記憶體管理,是由分段和分頁兩種技術共同奠基的。當然,也不能簡單地就說2者的粒度不同。兩者本質上都是對記憶體的劃分,我感覺分段是從...

java的String在記憶體中如何分配的?

陳肖恩 1.myString 是常量,通過編譯期直接定義到常量表中,new String 是執行期指令,基本上new出來的物件都在堆上。可以理解為編譯期常量表定義了 myString 字串,在執行期調取常量new出乙個String物件放到堆裡,兩者引用不一樣。2.字面量字串可以在編譯器優化,例如常量...