C語言再學習之內存對齊
昨天看Q3的代碼,看到有個_INTSAIZEOF的宏,著實暈了一陣。一番google后,終于明白,這個宏的作用是求出變量占用內存空間的大小,先看看_INTSAIZEOF的定義吧:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
(ANSI C標準下,_INTSAIZEOF宏定義在stdarg.h中,Q3中定義在bg_lib.h中;bg_lib.h -- standard C library replacement routines used by code)
關于這個宏的內部原理,我們后面再談,還是先理理“內存對齊”這詞的意思吧,多年來一直模糊的存在于我的大腦中,究竟為什么要內存對齊啊。
內存對齊的根源:
1、許多計算機系統對基本數據類型可允許地址作了一定的限制,要求某種類型對象的地址必須是某個值n(通常是2、4、8)的倍數,從而來簡化處理器和存儲器之間的接口的硬件設計。如Linux的對齊策略是2字節數據類型,例如short的地址必須是2的倍數。而較大的數據類型如:int、int*、float、double則必須是4的倍數。而Microsoft Windows的策略要求更為嚴格-----任何k字節對象的地址必須是k的倍數。比如要求一個double類型對象的地址必須是8的倍數(引自《深入理解計算機系統》)。
2、數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
內存對齊的規則:
上面所提到的自然邊界是由什么決定的呢,我想應該是由硬件平臺決定的,至于操作系統和編譯器(默認對齊系數)則都是依賴于上一層的。當然,編譯器的對齊系數可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變;
舉個例子:比如參數入棧,編譯器并不是一個緊挨著一個的壓入棧的,而是根據對齊系數來壓棧的,比如一個char類型的參數,雖然本身只占一個字節,但是編譯器會自動補全后面3個字節,然后再壓下一個參數。
(這里說點題外話:在寫過delphi程序的人都知道,有個packed的保留字,作用就是壓縮數據結構,不要按對齊存儲,除非數據結構體積龐大,否則為什么要用packed呢,用了不就影響內存讀取的速度了嗎?:))
1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之后,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
3、結合1、2顆推斷:當#pragma pack的n值等于或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。
(以上3點引自《也談內存對齊》一文)
再看_INTSAIZEOF宏:
這個宏的定義咋一看有點丈二和尚摸不著頭腦,不過網上有對齊的解釋,看后相信會豁然明朗了:
對于兩個正整數 x, n 總存在整數 q, r 使得
x = nq + r, 其中 0<= r <n //最小非負剩余
q, r 是唯一確定的。q = [x/n], r = x - n[x/n]. 這個是帶余除法的一個簡單形式。在 c 語言中, q, r 容易計算出來: q = x/n, r = x % n.
所謂把 x 按 n 對齊指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 這也相當于把 x 表示為:
x = nq + r', 其中 -n < r' <=0 //最大非正剩余
nq 是我們所求。關鍵是如何用 c 語言計算它。由于我們能處理標準的帶余除法,所以可以把這個式子轉換成一個標準的帶余除法,然后加以處理:
x+n = qn + (n+r'),其中 0<n+r'<=n //最大正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 <n //最小非負剩余
所以 qn = [(x+n-1)/n]n. 用 c 語言計算就是:
((x+n-1)/n)*n
若 n 是 2 的方冪, 比如 2^m,則除為右移 m 位,乘為左移 m 位。所以把 x+n-1 的最低 m 個二進制位清 0就可以了。得到:
(x+n-1) & (~(n-1))

浙公網安備 33010602011771號