在閱讀iptables內核模塊的代碼時,我遇到了這樣一個函數hweight32:html
unsigned int hweight32(unsigned int w) { unsigned int res = w - ((w >> 1) & 0x55555555); res = (res & 0x33333333) + ((res >> 2) & 0x33333333); res = (res + (res >> 4)) & 0x0F0F0F0F; res = res + (res >> 8); return (res + (res >> 16)) & 0x000000FF; }
乍看起來,彷佛很難理解這段代碼的功能,其實它就完成了「統計給定數字中值爲1的bit位個數」的功能,怎麼樣,是否是有點風馬牛不相及的感受。linux
下面咱們先看網絡上的一篇分析文章:網絡
============轉載:華麗的分割線============
函數
在 linux 內核裏,在計算 CPU 個數時用到了 hweight32 這個函數,這個函數就是漢明重量的計算方法。對於二進制串來講,它就是計算這個二進制串裏含有多少個 1 。hweight32() 函數實現以下:.net
/*代碼如上*/
下面先對一個任意的二進制串中 1 的數量的計算推出上面的公式:
1. 假設有 1 個字串爲:0110 1100 1011 1010,姑且將這個字串簡稱爲 A
2. 咱們先將 A 以每 2 位一組進行分組,即 01 10 11 00 10 11 10 10 ,而後計算出每組含有 1 的個數,方法以下:
<1> 首先用 0101 0101 0101 0101 即 (0x5555) 來和 A 相與:
0110 1100 1011 1010
0101 0101 0101 0101 &
---------------------------------
0100 0100 0001 0000 (將這個結果設爲 B)
通過上面的計算,實際上咱們已經得出了 A 中每小組中(每 2 位一組)的偶數位的 1 的個數,即(1,0,1,0,0,1,0,0)。
<2> 在步驟 <1> 中咱們算出了每組中偶數位 1 的個數,接下來要算出奇數位中 1 的個數,方法是先將 A 右移 1 位 (A >> 1),而後與 0x5555 再相與。A 右移 1 位就是將原來的奇數位變爲偶數位:
0011 0110 0101 1101 (A>>1)
0101 0101 0101 0101 &
---------------------------------------------
0001 0100 0101 0101 (將這個結果設爲 C)
<3> 如今,將 B 和 C 相加起來,就能獲得 A 中每 2 位爲一組的每組中的 1 的個數:
D = B + C
D = 0100 0100 0001 0000 + 0001 0100 0101 0101 = 01 01 10 00 01 10 01 01
(1 1 2 0 1 2 1 1)
好,如今咱們已經算出了 A 中以每 2 位分紅一組的每一個小組中 1 的個數。同理,接下來咱們要利用上面的結果(D)算出 A 中以每 4 位分紅一組中每一個小組中 1 的個數:
<4> 用 D 和 0011 0011 0011 0011 (0x3333)進行相與:
0101 1000 0110 0101
0011 0011 0011 0011 &
---------------------------------
0001 0000 0010 0001 (將這個結果設爲 E)
<5> 再將 D 右移 2 位,即 D >> 2 後,再和 0x3333 相與:
0001 0110 0001 1001 (D>>2)
0011 0011 0011 0011 &
------------------------------------
0001 0010 0001 0001 (將這個結果設爲 F)
<6> 將 E 和 F 相加:
G = E + F
G = 0001 0000 0010 0001 + 0001 0010 0001 0001 = 0010 0010 0011 0010
(2 2 3 2 )
也就是說,A 中以每 4 位分紅一組後,每小組中 1 的個數分別爲 2, 2, 3, 2
<7> 一樣的方法,下面再計算每 8 位一組的每小組中的 1 的個數 (逐步逼近)
先將 G 和 0x0F0F 相與:
0010 0010 0011 0010
0000 1111 0000 1111 &
--------------------------------
0000 0010 0000 0010 (將這個結果設爲 H)
同理再將 G 右移 4 位後與 0x0F0F 相與:
0000 0010 0010 0011
0000 1111 0000 1111 &
---------------------------------
0000 0010 0000 0011 (將這個結果設爲 I)
<8> H 和 I 相加後即得每 4 位分組後每小組 1 的個數:
J = H + I
J = 0000 0010 0000 0010 + 0000 0010 0000 0011 = 0000 0100 0000 0101
( 4 5 )
<9> 到此,之差最後逼近一步,即可獲得咱們想要的結果,如今將 J 與 0x00FF 相與:
0000 0100 0000 0101
0000 0000 1111 1111 &
--------------------------------
0000 0000 0000 0101 (將這個結果設爲 K)
<10> 將 J 右移 8 位後再與 0x00FF 相與:
0000 0000 0000 0100
0000 0000 1111 1111 &
---------------------------------
0000 0000 0000 0100 (將這個結果設爲 L)
<11> 將 K 和 L 相加:
M = K + L = 0000 0000 0000 0101 + 0000 0000 0000 0100 = 5 + 4 = 9
最終結果爲 9 ,這就是字串 A 中的 1 的個數。
code
============轉載: 華麗的分割線============htm
下面再來簡單的逐行分析一下:ip
unsigned int res = w - ((w >> 1) & 0x55555555);
將w按2bit爲一組劃分,統計每組中1的個數;get
這裏並無作兩次「位與&「的操做,其中蘊含的一個小技巧是:對於一個2bit的數字,其值減去它右移一位的結果就是這個2bit數字中1的個數,例如:it
11 - 01 = 10 (2):11中含有的1的個數爲2;
10 - 01 = 01 (1):10中含有的1的個數爲1;
01 - 00 = 01 (1):01中含有的1的個數爲1;
00 - 00 = 00 (0):00中含有的1的個數爲0;
res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
將第一步的計算結果以每4位爲一組,計算每組中1的個數;
res = (res + (res >> 4)) & 0x0F0F0F0F;
將上一步的計算結果以每8位爲一組,計算每組中1的個數;
因爲上一步中計算的是每4位爲一組的結果,所以其最大值爲4(0100),所以能夠直接相加而不用擔憂溢出,並將高四位歸0;
res = res + (res >> 8);
將上一步的計算結果以每16位爲一組,計算每組中1的個數;
return (res + (res >> 16)) & 0x000000FF;
將高16bit中1的個數與低16bit中1的個數相加,就獲得了最後的結果;
結論:請收下個人膝蓋!