接著前面的文章,這篇文章就來說說menory alignment -- 記憶體對齊.
一、為什麼需要記憶體對齊?
無論做什麼事情,我都習慣性的問自己:為什麼我要去做這件事情? 是啊,這可能也是個大家都會去想的問題,
因為我們都不能稀裡糊塗的或者。那為什麼需要記憶體對齊呢?這要從cpu的記憶體訪問機制說起.
為了瞭解清楚cpu的記憶體訪問機制,昨天整晚都在尋找資料,但是還是找不到很好的介紹資料.後來只是找到了相關
的一些介紹的部落格。 這些部落格中大多都是以介紹記憶體對齊為主要目的,然後順帶著說一下cpu的記憶體訪問機制,所以
找不到權威的資料,後來聽說<<組合語言編程藝術>>這本書裡面有關於x86的系統介紹,就下載了一份PDF,但是
也還是沒有找到.
所以呢下面的一些關於x86的記憶體訪問方面的只是很多都是來源於一些比較好的部落格.在文章的最後我會註明參考的
部落格連結,作為擴充閱讀.
簡單介紹x86的記憶體訪問機制:
1.記憶體的寫入操作: cpu把需要寫入的地址放入地址匯流排, 把需要寫入的資料放入資料匯流排, 把控制匯流排置為寫入操作.
然後記憶體子系統根據地址匯流排選定記憶體單元, 檢查控制匯流排發現是寫入操作,則入去資料匯流排資料, 寫入相關記憶體
單元.
2.記憶體的讀入操作: cpu把需要讀入的地址放入地址匯流排, 把控制匯流排置為讀入操作. 記憶體子系統根據地址匯流排選定記憶體
單元, 檢查控制匯流排發現是讀入操作, 則讀取記憶體單元中的資料, 寫入資料匯流排.
16bit資料匯流排: 每個記憶體周期,cpu只能讀取一個偶單元和一個奇單元,地址匯流排的地址是偶單元的地址,所以地址匯流排的地址永遠是2對齊的.
每個記憶體周期,可以讀取一個字,也就是16bit.
1.讀取一個字,如果是以2對齊的,則只需要一個記憶體周期即可完成.如果資料不是以2對齊的,則需要2個記憶體周期.
2.讀取雙字: 如果是以2對齊的,則只需要2個記憶體周期即可完成,如果資料不是以2對齊的,則需要3個記憶體周期完成.
32bit資料匯流排: 每個記憶體周期,讀取的資料地址都是以4對齊的.一個記憶體周期可以讀取一個雙字,也就是32bit.
1.如果讀取一個雙字,地址是以4對齊的話,則只需要一個記憶體周期即可完成.如果不是以4對齊,則需要2個記憶體周期完成.
2.如果讀取一個字,地址是對4模數餘3的話,那麼需要2個記憶體周期完成對資料的讀取.地址如果對4去模不餘3的話,則
只需要一個記憶體周期即可完成資料讀取.
3. 對於位元組, 任何位元組地址讀取只需要一個記憶體周期.
通過上面可以看得出,為什麼16bit資料匯流排cpu是以2對齊的,而32bit資料匯流排cpu是以4對齊的. 最主要的原因是能夠在最小的
記憶體周期內完成對地址的訪問,提高cpu的效率.
二、記憶體對齊的作用
如果不採用記憶體對齊機制的話,有些地址的訪問需要在多個記憶體周期內完成,而且還需要多次記憶體周期讀取的高低位元組
進行拼湊,然後得到32bit資料. 如果使用記憶體對齊機制,不僅可以減少對地址訪問過程中需要的記憶體周期,而且還避免了
高低位元組的資料拼湊,提高了cpu的工作效率.
三、編譯器是如何處理記憶體對齊的?
struct mem_alignment{ char a; int b; char c;};
在32位x86機器上面它的大小是12. 另外一個問題,如果結構體中的成員變數順序不一樣會導致該結構在記憶體中的長度
也不一樣,就像上面,如果改成下面這個樣子:
struct mem_alignment{ char a; char c; int b;};
那麼它的大小就變成了8.
如果我們使用緊湊的對齊 __attribute__((packed)) or __attribute__((aligned (1)))的話,
那麼struct mem_alignment的大小應該是6. 或者是使用偽指令#pragma pack (1).
#pragma pack (1)struct mem_alignment{ char a; char c; int b;};#pragma pack ()
上面最後一句的作用是恢複編譯器預設的對齊.
關於記憶體對齊方面的知識就總結到這裡. 也算是對前面文章的交代了~
參考資料:
<<從80X86結構看記憶體對齊問題>> http://my.unix-center.net/~Simon_fu/?p=262
<<oschina 記憶體對齊的問題>> http://www.oschina.net/question/234345_48055
<<Thinking in linux C/C++位元組對齊詳解>> http://www.linuxsong.org/2010/09/c-byte-alignment/