接着前面的文章,這篇文章就來說說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/