關於內存對齊的學習筆記

一、問題的提出
        兩年之前我寫過一篇可變參數學習筆記,裏面曾經簡單的解釋過一句:
        代碼
        ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
        的作用是在考慮字節對齊的因素下計算第一個可變參數的起始地址。
        當時限於時間和水平,未能做更詳細的解釋。
        今天(2007-11-26)在csdn論壇上看到了一個帖子
        http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257
問題:CRT源碼分析中一個關於可變函數參數的問題    
提問者:Sun_Moon_Stars
裏面又問到了這個宏,於是決定抽出半天時間,把這個問題詳細的說清楚。也算是把我的那篇文章
做一個完美的結尾。
     

二、引子
      先看一個日常生活中的問題,
問題1:假設有要把一批貨物放到集裝箱裏,貨物有12件,
一個箱子最多能裝6件貨物,求箱子的數目。
解答:顯然我們需要12/6=2個箱子,並且每個箱子都是滿的。這個連小學生都會算:-)

問題2:       把問題1的條件改一下,假設一個箱子最多能裝5件貨物,那麼現在的箱子數是多少?
解答:       12/5=2.4個,但是根據實際情況,箱子的個數必須爲整數,(有不知道這個常識的就不要再往下看了,
回小學重讀吧,呵呵)自然我們就要取3,
      下面把問題一般化

三、一般數學模型
問題3:設一個箱子最多可以裝M件貨物,且現有N件貨物,
則至少需要多少個箱子,給出一般的計算公式。
這裏要注意兩點
1、箱子的總數必須爲整數
2、N不一定大於M,很顯然,即使N <M,也得需要一隻箱子              

四、通項公式
1、預備知識
在討論之問題3的解答之前,我們先明確一下/運算符的含義。
定義/運算爲取整運算,即
對任意兩個整數N,M,必然有且只有唯一的整數X,滿足
X*M   <=   N   <   (X+1)*M,那麼記N/M=X。
這個也正是c裏/運算的確切含義。x的存在性和唯一性的嚴格證明可以見數論教材。
以後如無額外說明,/運算的含義均和本處一致。

/運算有一個基本的性質
若N=MX+Y,則N/M=X+Y/M,證明略

注意:N不是可以隨便拆的,設N=A+B,那麼一般情況下N/M   不一定等於   A/M+B/M,
如果A和B至少有一個是M的倍數,才能保證式子一定成立。


2、分步討論
根據上面的/運算符的定義,我們可以得到問題三的解答,分情況討論一下
已知N/M=X,那麼當
(1)、當N正好是M的倍數時即N=M*X時,那麼箱子數就是X=N/M
(2)、如果N不是M的倍數,即N=M*X+Y(1 <=Y <M)時
      那麼顯然還要多一個箱子來裝餘下的Y件貨物,
      則箱子總數爲X+1   =   N/M+1

3、一般公式
上面的解答雖然完整,但是用起來並不方便,因爲每次都要去判斷N和M的倍數關係,
我們自然就要想一個統一的公式,於是,下面的公式出現了
      箱子數目爲     (N+M-1)/M
 
這個式子用具體數字去驗證是很簡單的,留給讀者去做。
我這裏給一個完整的數學推導:
現在已經假定   /運算的結果爲取整(或者說取模),即
N/M=X,則XM   <=N   <(X+1)M
那麼,
(1)、當N=MX時,(N+M-1)/M=   MX/M+(M-1)/M=X
(2)、當N=MX+Y(1 <=Y <M)時,
   由1 <=Y   <   M,同時加上M-1,得到M   <=   Y-1+M   <=   2M-1   <2M
        根據   /運算的定義   (Y-1+M)   /M   =   1

  所以 (N+M-1)/M   =   (MX+Y+M-1)/M=   MX/M+(Y+M-1)/M=   X+1
顯然   公式   (N+M-1)/M與2中的分步討論結果一致。
可能有的讀者還會問,這個公式是怎麼想出來的,怎麼就想到了加上那個M-1?
這個問題可以先去看看數論中的餘數理論。


五、對齊代碼的分析        
有了上面的數學基礎,我們再來看看開頭所說的對齊代碼的含義
      ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
意義就很明顯了        
這裏。機器字長度sizeof(int)相當於箱子的容量M,變量的真實字節大小相於
貨物總數N,整個代碼就是求n所佔的機器字數目。

順便仔細的解釋一下
~(sizeof(int)-1))

這裏用到了一個位運算的技巧,即若M是2的冪,M=power(2,Y);

則N/M   =  N>>Y  ,

另根據數論中的餘數定理,

有N=M*X+Z(1 <   =Z <  M)
而注意到這裏的N,M,Z都是二進制表示,所以把N的最右邊的Y位數字就是餘數Z.
剩下的左邊數字就是模X.

而內存對齊要計算的是佔用的總字節數(相當於箱子的最大容量),所以

總字節數 = ( N/M)*M =( N>>Y)<<Y

注意,這裏的右移和左移運算並未相互抵消,最後的結果實際上是把N中的餘數Z去掉(被清0),

而左邊模X得以保持不變。

而當M = power(2,Y) 時

(N >>Y) << Y = (N   &(~(M-1))也是一個恆等式(這個讀者也可以用數字驗證),

所以,就得到我們前面看到的宏

 ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1)) 



注意:
(1)這裏最關鍵的一點就是M必須是2的冪(有人常常理解成2的倍數也可以,那是不對的),
否則上面的結論是不成立的
(2)   ~(M-1)更專業的叫法就是掩碼(mask)。因爲數字和這個掩碼進行與運算後,數字的最右邊Y位的
數字被置0("掩抹"掉了).即掩碼最右邊的0有多少位,數字最右邊就有多少位被清0。

小結:
1、字節對齊的數學本質就是數論中的取模運算。在計算機上的含義就是求出一個對象佔用的機器字數目。
2、在數學上看內存計算的過程就是先右移再左移相同的位數,以得到箱子的最大容量。

3、在c中/運算可以用位運算和掩碼來實現以加快速度(省掉了求位數的過程),前提是機器字長度必須爲2的冪。

 
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章