contributed by < YiChainLin
>
實驗的 gcc 編譯器版本
得出的結果為:
a + b
的時候造成了 overflow
的結果,因此 a + b
的值為 6000000000 - 4294967295 = 1705032705
,除二後得到 852516352
的結果overflow
的方式overflow
:overflow
方式以 710 = 01112 為例:
左移 : 01112 << 1 = 11102 = 1410
將位元向左移,並在右邊補0,在10進制中等價為乘2
右移 : 01112 >> 1 = 00112 = 310
將位元向右移,並在左邊補0,在10進制中等價為除2
&
運算子確認 a & b
是否進位,若有進位則為 1 若無則 0&
運算,若有進位表示在移位時,進位的值不可忽略,要補 1 回去EXP1 = a & b & 1
以下使用 4 位元的數值進行平均數的舉例
a = 1010 = 10102 , b = 610 = 01102
得到的結果為 10002 = 810
overflow
另一種方式EXP2 = a & b
EXP3 = a ^ b
使用 XOR
邏輯運算子可計算出兩數相加的結果,但不進位,所以進位的部分要透過 AND
運算子計算在配合右移,因此先思考兩數的相加為:
以下使用 4 位元的數值進行平均數的舉例
a = 1010 = 10102 , b = 610 = 01102
得到的結果也為 10002 = 810
改寫〈解讀計算機編碼〉一文的「不需要分支的設計」一節提供的程式碼 min,我們得到以下實作 (max):
相關對於 XOR
運算子讀物: That XOR Trick
XOR
運算子 Toggle (切換)特性XOR
會抵消,舉例來說, a ^ a ^ b
(交換律)-> b ^ a ^ a
為 b 對 a 做了兩次的切換結果會得到 b 自己XOR
運算得出 a 或 b
EXP4
中為 a ^ b
EXP5
要能在 a ^ b
中得到 0 或 a ^ b
的結果
a ^ b
& -1 = a ^ b
a ^ b
& 0 = a ^ b
-
將 1 轉 -1如何得到 111… 的二進制數值遮罩,在看到題目的引數值為 uint_32 也就是32 位元的無號整數,因此思考到關係運算子( relational-expression,>、<…) 出來的值為是否為有號或無號的整數
參考ISO/IEC 9899:1999 規格書的 6.5.8 中的第 6 點
- Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.) The result has type int.
指出關係運算子得出來 1 或 0 型態為 int ,為有號的整數
所以在題目中的 a < b
判別式中得出的 1 或 0 為有號的整數,而在 -1 的二進位表示方式為 111111… 為 1 的遮罩方式
EXP4 = a ^ b
EXP5 = a < b
XOR
運算得出較大的值a > b
中的敘述中就能判斷出兩數的大小,所以在此函式即能判斷出兩數的最大值,因此修改引數的宣告考慮以下 64 位元 GCD (greatest common divisor, 最大公因數) 求值函式:
改寫為以下等價實作:
COND = v
RET = u << shift
根據 GCD 演算法:
OR
運算子得到另一個非 0 數值再來為非偶數的輾轉相除法( GCD )過程
while
迴圈消除COND = v
shift
變數紀錄RET = u << shift
x86_64
上透過 __builtin_ctz
改寫 GCD
,分析對效能的提升__builtin_ctz
函式用於找出最低位的 1 後面有多少 0 的個數Built-in Function: int __builtin_ctz (unsigned int x)
Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined. gcc 文件說明
__builtin_ctz
改寫後的程式運行表現cache-misses
的發生,整體而言使用 __builtin_ctz
改寫後程式的運行有較好的改善在影像處理中,bit array (也稱 bitset) 廣泛使用,考慮以下程式碼:
用以改寫的程式碼如下:
其中第 9 行的作用是找出目前最低位元的 1,並紀錄到 t 變數。舉例來說,若目前 bitset 為 000101b,那 t 就會變成 000001b,接著就可以透過 XOR 把該位的 1 清掉,其他保留 (此為 XOR 的特性)。
EXP6 = bitset & -bitset
參考這篇文章 : Find the lowest set bit
AND
運算,找出最低位元的數值以下是 LeetCode 166. Fraction to Recurring Decimal 的可能實作:
hash table
、 linked list
的實作進行n/d
所產生的整數值, 4 / 333 = 0
,透過sprintf
函數寫入整數值在 p
中,最後透過移位加入 .
字元(小數點)#include <stdio.h>
int sprintf(char * restrict s,const char * restrict format, …);
Description:
The sprintf function is equivalent to fprintf, except that the output is written into
an array (specified by the argument s) rather than to a stream.
hash table
,主要用於儲存計算的結果,與循環小數的查找struct rem_node *node
key
為每一輪小數除法的餘數值(模除)index
為紀錄整個 for
迴圈的輪次link
為 struct list_head
結構用於資料的指標連接4 / 333
中的第一位小數為 0
, remainder
為 40
在 for
迴圈的 i = 0
的輪次,也就是 key = 40 index = 0
,將結果加入到 hash table
中MMM = list_add 為加入 hash table
的巨集
EEE = &head[remainder % size] 加入的位置在對應模除的位置
(EEE 參考 laneser(quiz2)同學的開發紀錄)
(remainder * 10) / d
加入在 q
中,但是 q
的型態為 char *
需要將算出來的商數轉換成字元型態才能加入+ '0'
的一個敘述,這是一個將整數轉字元的技巧,觀察其 ASCII 碼就會發現在 48~57
區間的顯示字元為 '0'~'9'
, 而 '0'
所代表的值為 48
remainder = (remainder * 10) % d;
更新下一輪的餘數值進行運算,如果 = 0 則表示已除盡q
每一輪會存取該輪的除法的商值, 以 4 / 333
來說經過 3 輪存下了 0
、 1
、 2
4 / 333
經過四輪後得到像這樣的 hash table
:find
函數,利用 hash table
找尋是否會有餘數相同的情況,也就是在 hash table
在任意 head[i]
中找到有相同 key
(相同餘數)head[4]
中透過 list_for_each_entry
走訪可以得到會有相同的 key
發生,因此返回當前的 index
值,"返回第一次 key
的 index
值",範例中返回 index = 0
index
值意義在於不重複的循環小數值的數量,例如:可能會有結果為 "0.23(45)"
其中 2
、 3
不為循環小數,此時在 find
的函數值會返回 2list_for_each_entry
對一個 linked list
的中,對每一個成員的起始位址進行訪查,所以能得到結構內部的資料find
函數找出不重複的循環小數值的數量,在 3~4 行中進入 while
迴圈要填入未循環的小數(商)值q = decimal
)'\0'
4 / 333
的範例中得出的結果為 "0.(012)"
PPP = pos– 為每一次填入未循環小數就會遞減已填入的數量
malloc
配置記憶體,但是卻沒有釋放,因此需要釋放掉整個 hash table
所佔的記憶體空間(包含 head
與 node
)list_for_each_safe
走訪每一個 heads 中所連結的 node 資料,並將其釋放
cur
可以記住走訪點的下一個位置,避免發生錯誤,在走訪的過程中較安全(也因此有 safe 敘述)list_for_each_safe
函式,參考 list.hreturn result;
前加入釋放記憶體的函式__alignof__
是 GNU extension,以下是其可能的實作方式:
請補完上述程式碼,使得功能與 __alignof__
等價。
byte
長度,透過記憶體對齊的方式&((struct { char c; t _h; } *)0)->M)
在這個敘述中目的要取得 t
的記憶體位置,因此 M
為 struct
結構中 t
型態的便數成員,所以 M = _h
X = 0
表示該結構的起始位置(也就是 char 的位置),計算後就可以得到引數的占用記憶體長度M = _h
X = 0
__alignof__
的使用案例 2 則,並針對其場景進行解說__alignof__
unsigned int
與 unsigned long
,第二個函式分別為 unsigned int
與 long
主要是將 unsigned int
轉換成 long
的型態變換if-else
判斷系統的 long
與 long long
分配的記憶體長度,進而選擇要對哪一種的型態進行記憶體對齊LP64 model
下執行,因為 long
與 long long
都是 64-bits
所以會執行 if 的敘述使 32-bits
的 int
與 long long
轉換,並記憶體對齊LLP64 model
則 long
與 long long
分別為 32-bits
與 64-bits
,此時會執行 else 敘述,使 32-bits
的 int
與 long
轉換,並記憶體對齊
const char *s
用途與題目的 char c
一樣,編譯器會挑選占用較大的記憶體型態進行分配考慮貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
以下是利用 bitwise 和上述技巧實作的 FizzBuzz 程式碼: (bitwise.c):
is_divisible
函數,功能為判斷是否為被整數 n 整除
length
中要決定字串的長度,最多為 8 ,所以要讓 length
達到 8 的可能為 2 shift 2 次,也就是若 div3
與 div5
都返回 bool
的 1 就可以決定要顯示多少長度的字串,分別為 2 (印出數字本身) 、 4 (Fizz 或 buzz) 、 8 (Fizzbuzz)兩者個結果可交換,不管是否為 3 或 5 的倍數,都透過 is_divisible
函式判斷是否要 shift
KK1 = div3
KK2 = div5
8 >> div5
若 div5 = 1
則會等於 4 會從 [4] 的位置開始印,若 div5 = 0
則會從 [8] 的位置開始印 "%u" 也就是原始數字div3 = 1
可透過左移 2 位得到 4KK3 = div3 << 2
在下列程式碼中第 9 行考慮到字串的位置 F 的位置是 [0] 、B的位置是 [4]、%的位置是 [8],要印出數字應該要是 "%u" 要從第 [8] 的位置印出
原程式碼為 9 會從 "u" 開始印,則會顯示不出原始數字