contributed by < Uduru0522
>
perspective and application of computer systems 2020
由於 bitwise operators 僅定義在 integer 之上,
C99 Standard, §6.5.7, §6.5.10~6.5.12
Constrains: Each of the operands shall have integer type.
因此進行強制轉型。此種把某型別的值當作其他型別來使用的行為,稱作 Type Punning
然爾,在搜尋關於這種轉型的資訊時,發現這種轉型會違反 Strict Aliasing Rules,有可能造成 Undefined Behavior。
C99 Standard, §6.5/7
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object,
- a qualified version of a type compatible with the effective type of the object,
- a type that is the signed or unsigned type corresponding to the effective type of the object,
- a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively,amember of a subaggregate or contained union), or
- a character type.
以上規則被稱為 Strict Aliasing Rules,
否則可能由於 Compiler 在 -fstrict-aliasing 啟用時(e.g., -O2),因默認不會有除了符合上述規定的兩個相異指標指向同一個記憶體位置 (aliasing),而造成非預期的結果.
範例
預想
在 foo()
中,由上至下執行,x
的記憶體內容被修改至 1,再修改成零,最後回傳。
測試
分別以 gcc -o a1.out <filename>
以及 gcc -o a2.out <filename> -O2
編譯並執行,可以得到兩種不同的結果,如圖:
分析
至 Compiler Explorer 上進行比較,觀察啟用 -O2 的結果:
可以看到,<foo>
之中,因編譯器默認我會遵守 Strict Alias Rule,
假定 *f
以及 *i
不會指向同一記憶體,
a.k.a. 可以直接在修改 *i
後扔至 $eax。
memcpy()
–- C: Valid, C++: Validunion
C: Valid, C++: UB以下為 bfloat16 的數字表達形式:
Exponent | Sigificand == 0 | Significand != 0 | Equation |
---|---|---|---|
Denormalized | |||
Normalized | Normalized | ||
Not a Number | –- |
此程式碼使用四捨五入進至小數點後第七位的方式處理多餘的 bit.
設 x
為 ,r
便會是 ,又或者可以說是 。
直接加上 x
之後,就有四捨五入的效果。
最後,再以 0xFFFF0000
取得前十六個位元回傳。
以 gcc -E -P ring_buffer.c > ring_buffer_extended.c
將上方測試程式展開進行說明。
-E: 僅 Preprocess
-P: Preprocess Option, 不在產出的信息中標注展開來源
此程式對 Ring Buffer 進行實作 (又稱為 Circular Buffer),
我們可以當作一個有大小上限的 queue,當其為滿時,
push 會覆蓋住原先會第一個被 pop 掉的元素。。
此實作以 start
指向起點(下次提取的位置),end
指向尾端(下個填入的位置)。
利用 macro,我們便可定義任意一種 type 的 Ring Buffer。
初始化時,我們取得 n + 1 個元素大小的記憶體,如此一來,配合而後的程式碼,
僅有 Buffer 為空時會發生 start == end
的情況。
初始化完成後,填入 "37":
end
換至下個位置,並判斷 Out Of Bound假設以下的 Buffer 狀態,欲寫入 "96":(大小 = 3)
end
所在位置end
start == end
時,移動 start
如此,由於我們提取元素時從 start
開始,形同 13 被覆蓋掉了。
start
所在位置的元素,並向前挪移。我們可以看到,assert(EX)
被展開成以 ?:
以及 _assert()
函式 組成。
打開 <assert.h>
,可以找下定義,
其中可以發現一個 (void)0
,搜尋資料後發現,它可以被用在『需要有 Expression,卻不需要在該處做任何事』的狀況。
舉例來說,可以用來定一個僅在 NDEBUG 沒被定義時啟用的函式:
假設我們定義一個如下的 macro:
使用時,用以下的方式:
在 macro 定義的最後不補分號 ;
,可以讓它和呼叫函式時的寫法一致
然而可能會造成這種狀況:
展開後會變成如下:
很明顯,與預期的不符。因此,在有定義多個 statement 的 macro 時,我們以 do...while(0)
包住,就可以達成以下樣子:
{}
就好?若我們使用一組大括號,原本的 macro 展開之後會變成這個樣子:
在 if
block 之後必定會出現一個大括號,造成 dangling else的情形。如過要避免這個狀況,我們就不能在使用 macro 時後方加上分號,然而會造成程式碼格式不統一的情況。do...while(0)
身為一個必須在後方存在一個分號的 block 來說最適合這個角色。
//TODO
查詢對 macro 該使用甚麼動詞 (不是 call)
用 "expand" –jserv
linked-list 不特別說明。
此時作的特點在於初始化串列時使用 compound literal 來進行。
引述 C++ Refrence - C Language / Expressions
Constructs an unnamed object of specified type in-place, used when a variable of array, struct, or union type would be needed only once.
舉例來說,以下程式碼:
和以下相同:
其構造中的 initializer-list 需根據 type 的構造,依序填入符合的 Initializer 並以逗號隔開,
各項有以下三個選擇:
expression
{ initializer-list }
designator expression
(After C99),用來自訂填入順序。會自該項繼續初始化。首先,指定初始化 foo_d
後繼續往下初始化 foo_b
以及 foo_arr
,最後再指定 foo_i
。
foo_b
為一 structure,初始化需要再以 {}
圍繞{}
圍繞除此之外,Compound Literal 還有以下特性:
以上函式回傳 1
若第 7 行使用while(){}
,由於會造成 block 結束,Compund Literal 的生命週期結束,
*p
會成為 danglling pointer。
我們可以將以下 macro 的使用
視作:
因此可以使用 cons()
來進行初始化。
此處在對 linked-list 排序的操作,以 insertion sort 進行。
sort()
中,以 llist *sorted
儲存以排序完成的部分,且排序為由小排到大。
每個 iteration 將原本的 head
切離,放到 sorted
之中。
此題輸入的陣列可以用以下的方法生成:
由此出發,我們可以發現,對每個 bit 位置來說,操作後, 的 set bit 之處,總 set bit 必定相等或者更多。
舉例來說, ( 為 2)
SetBit | [2] | [1] | [0] |
---|---|---|---|
Before | 1 | 4 | 4 |
After | 0 | 5 | 3 |
程式以 c1
計算操作前,c2
計算操作後。
利用 8 * sizeof(int)
應對 int
非為 32-bit 的情況。
且根據 n, 僅計算會有影響的 bit.
以下為題目程式碼於 LeetCode 上的執行結果。
觀察 for
迴圈,我們可以看到有大量的 branch 指令會出現,且因為數列沒有規律,
我們可以預想 prediction faliure 會發生許多次。
可以嘗試移除 if:
不過並未影響 LeetCode 上程式執行時間
可以考慮使用類似 c++ 中 std::map
的方式實作,逕直開一個夠大的 array 省去 hash function: