contributed by < jj97181818
>
~0UL
有 64 bit 的 1。((~0UL) >> (LEFT))
,為了讓連續的 bitmask 最高位到 h
停止,又因 LSB 從第 0 位開始算起,MSB 是第 63 位,所以需要讓 64 bit 的 1 向右移 63 - h
位。((~0UL) >> (l) << (RIGHT))
,先將 64 bit 的 1 向右移 l
位,為了讓連續的 bitmask 最低位從第 l
位開始,就再左移 l
位,其餘會補 0,就實現從第 l
位開始。((~0UL) >> (LEFT))
和 ((~0UL) >> (l) << (RIGHT))
做 AND 運算,即可同時滿足最高位到 h
、最低位從 l
開始,即 GENMASK 巨集。因此 LEFT
= 63 - h
, RIGHT
= l
to_fd
要將指定的位址 v 當作 fd 的起始位址,且要確保以 4 bytes 向下對齊,而向下對齊的巨集:v & ~(4 - 1)
= v & ~3
= v & 1100
透過將位址 v 的最後兩位 clear 掉,來保證該位址一定為 4 的倍數,做到向下對齊。
(struct foo *)
。因此 EXP1
= (struct foo *)(v & ~3)
目標是從 12345678 變成 87654321(這裡的 1~8 都是代表著 0 或 1 的值,只是用 1~8 來表示比較能清楚看出 reverse 的過程):
x = (x >> 4) | (x << 4);
這行可以看出從 8 bit 的中間切成兩半,並互換。
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
這行接著上一行切兩半的方式,先用遮罩 0xCC 取得原本位在 1, 2, 5, 6 的值,那 EXP2
的位置一定需要保留 3, 4, 7, 8 的位置,才能讓 x 保留原本 1~8 位置的所有值,因此這裡選用 0x33 當作遮罩,並左移兩位,在與 ((x & 0xCC) >> 2)
做 OR 運算時,即可保留所有資訊,並切從 4 bit 中間切成兩半做互換。
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
這行與上一行同樣的概念,只是改為每兩個 bit 中間切兩半做互換,可以看到這裡用的遮罩為 0xAA,並右移一位,而另外一個遮罩可以推得是 0x55
,同樣要記得左移一位,就完成互換的動作了。從最初的 12345678 變成 87654321。
因此 EXP2
= (x & 0x33) << 2
, EXP3
= (x & 0x55) << 1
逗號運算子:(expression1, expression2)
首先計算表達式 1,然後計算表達式 2,並為整個表達式返回表達式 2 的值。
foreach_int
macro 中,有個 for 迴圈:unsigned _foreach_i = (((i) = ((int[]){__VA_ARGS__})[0]), 0)
((int[]){__VA_ARGS__})[0]
為整數陣列,其中 __VA_ARGS__
會填入 foreach_int
從第二個以後的參數,當作整數陣列的值,並取得整數陣列的第 0 個值,然後將值 assign 給 i
_foreach_i
_foreach_i < sizeof((int[]){__VA_ARGS__}) / sizeof(int)
sizeof((int[]){__VA_ARGS__}) / sizeof(int)
會得到整數陣列的元素數量_foreach_i
小於元素數量時繼續執行迴圈(i) = ((int[]){__VA_ARGS__, 0})[EXP4]
_foreach_i
先加 1,然後就可以讓 i 取得下一個整數陣列的元素++_foreach_i
foreach_ptr
macro 中,也有個 for 迴圈:unsigned _foreach_i = (((i) = (void *)((typeof(i)[]){__VA_ARGS__})[0]), 0)
((typeof(i)[]){__VA_ARGS__})[0]
是一個型別為 typeof(i)
的陣列,然後取出陣列第 0 個位置的值,這個值可以預期傳入的是位址,因為這個巨集的名稱是 foreach_ptr
((i) = (void *)((typeof(i)[]){__VA_ARGS__})[0])
將值強轉型為指向 void 的指標,然後 assign 給 i
。_foreach_i
(i)
i
不為 0 就會繼續執行迴圈++_foreach_i
因此 EXP4
= ++_foreach_i
, EXP5
= ++_foreach_i
signal
來紀錄相除結果的正負號,然後如果除數或被除數為負整數,一律改為正整數。shift
從 0 開始,當 dvd > (EXP6)
時,shift 加 1,因為這題目的是要做除法,這裡的 EXP6 一定會和除數 dvs
有關。這個迴圈會在 shift
愈來愈大之後在某個條件下停止,可想到的是將 dvs 左移 shift 位,當 shift 愈大,dvs
也愈大,直到超過或等於 dvd
的大小。這裡 shift
的意義是類似商的概念,看除數乘以多大的倍數(左移多少位)能夠比被除數大或至少相等。dvd
仍大於等於除數 dvs
時,表示可以繼續進行除法。當 dvs
左移 shift
位會比 dvd
大時,就將 shift
減 1。1 << shift;
的想法是將小於 dvd
並左移過後的 dvs
只取其最高位元,用 1 左移 shift
位來表達。dvd
小於 dvs
才會停止,EXP7 要更新 dvd
,而 dvd
減去 dvs
左移 shift
位,就是更新被除數,讓它減去除數乘以一個倍數。dvd
不再大於等於除數 dvs
時,相除的結果如果超出能表達的最大值,就直接回傳 INT_MAX。其餘則回傳除完的結果。因此 EXP6
= dvs << shift
, EXP7
= dvd -= dvs << shift
限制:
ilog32 與 ffs 的實作很像,只是 ffs 是從 LSB 開始找第一個為 1 的 bit,這裡是從 MSB 找第一個為 1 的 bit。
ret = v > 0
是要記錄當 v > 0 時,最高位的 1 也需要一個位元儲存。m
= 16),然後將 v 右移 16 位,為的是方便繼續觀察原本較高位元的 16 bit 中還需要多少 bit 儲存。接著 ret 會記錄到目前為止共需要 16 bit 來存這個 v。m
= 8),然後將 v 右移 8 位,方便繼續觀察原本較高位元的 8 bit 中還需要多少 bit 儲存。然後 ret 記錄到目前為止共需要 16 + 8 bit 來存這個 v。(v > 0x3) << 1
才能查看較高位元的 2 bit 中是否有 1。ret
= 1 就有加上一位了,所以只需要再加一位,EXP11 為 v > 1 時 ret
加上 1。因此 EXP10
= (v > 0x3) << 1
, EXP11
= ret += v > 1
向上對齊:
向上對齊是利用位運算的方法,計算記憶體對齊後的大小,這裡的 MAX_ALIGNMENT 是 16 bytes,向上對齊會以 16 的倍數往上取。
例如:原本大小(這裡的 x)為 1~16 的變數,在向上對齊之後大小都會變成 16,17~32 會變成 32,33~48 會變成 48。
左段的程式 ((x) + MAX_ALIGNMENT - MMM)
是將大小加上 alignment 的大小再減去 MMM,要讓原本大小不足 16 的都補到 16。MMM 應該為 1,因為當 x = 16 時,x + MAX_ALIGNMENT = 16 + 16 = 32,會讓 16 在對齊後變成 32。
右段的程式 ~(NNN)
是要去除不足 16 的餘數,因為最後算出來的值應該是 16 的倍數。
當 NNN
= MAX_ALIGNMENT - 1
時:
~(MX_ALIGNMENT - 1)
= ~(15)
= 11110000
((x) + MAX_ALIGNMENT - 1) & ~(MAX_ALIGNMENT - 1)
= (x + 16 - 1) & 11110000
= (x + 00001111) & 11110000
因此 MMM
= 1
, NNN
= MAX_ALIGNMENT - 1
向下對齊:
因為向下對齊是將大小為 1~15 對齊成 0,16~31 對齊成 16,所以不需要加上 MX_ALIGNMENT 來補足不到 16 的餘數,不足的都直接不要了。
並說明 round-up/down 的應用場合
在 linux/include/linux/netfilter_bridge/ebtables.h
的第 107 行 :
EBT_ALIGN 是做向上對齊。
限制:
((typeof(x)) -1) > 0
在 x 的型別為 unsigned 的時候才會為 true((typeof(divisor)) -1) > 0
在 divisor 的型別為 unsigned 的時候才會為 true(((__x) > 0) == ((__d) > 0))
在兩個值皆大於 0 或小於 0 的時候,才為 true,也就是兩者同號為 true((RRR) / (__d))
,即被除數或除數其中一個為 unsigned,或是兩者皆為有號數,但是同號。((SSS) / (__d))
,即兩者皆為有號數,且不同號。RRR
, SSS
各自要怎麼表達才能做到上面的想法。因為 ((RRR) / (__d))
中我能改變的只有 RRR 的部分,所以我做了一些調整,變成:因此 RRR
= (__x) + (__d) >> 1
, SSS
= (__x) - (__d) >> 1
fls 是找最高位 1 的位置。
假設今天要求 的平方根 N,
定義 ,其中 會是 或 (用二進制的概念),
要從 一直迭代算到 ,慢慢逼近要求的 N( 會愈來愈靠近 N):
由上可知:,而 會是 或 。
如果 (加上了 也沒有超過 ),則 ,且 ;反之,則 ,且 。
為了避免在每一輪算 的時候都要用 的平方去決定 是 或 ,改用 (該輪 和原本的 還差多少,愈小代表 愈靠近 的平方根),
然後 (因為 可能會比 來的更靠近 N,所以在與 的差異上, 可能會比 小,其中 是 和 的差,是大於等於 0 的),
而 。
上面有寫到會從 一直迭代算到 ,首先是 ,為了確認 是 還是 ,將其代入 ,所以確認 。
前面有提到 ,將其分成 和 兩部分:
(在確定 為 而不是 後, 代入 )
更新 , :
當 此時 c 就是要求的平方根。
將程式中的 b
看成上述的 ,m
看成上述的 ,x
看成上述的 ,y
看成上述的 ,程式就是在迴圈不斷地更新 和 。
因此 XXX
= y >>= 1
, YYY
= x -= b
, ZZZ
= m >>= 2