contributed by < MuChengChen >
EricccTaiwanshell=,使用 shell 即可。queue.c 的筆記,建議補上各自功能的 commit,方便讀者們搭配著筆記閱讀程式碼。Step 1:增加源專案到 remote 並命名為 "upstream"
git remote add upstream https://github.com/sysprog21/lab0-c.git
Step 2:fetch 源專案的所有分支
git fetch upstream
Step 3:利用 git rebase 將本地的 master 改寫為源專案的 master
git rebase upstream/master
Step 4:將改寫的本地 master 推回自己帳號的專案
git push origin master --force
q_new創造空佇列
malloc 取得 list_head 結構體大小的記憶體空間並配置給新的 list_head 結構體指標,若記憶體取得失敗則回傳 NULLINIT_LIST_HEAD 初始化 list_head 結構體指標,將指向的結構體的 next 和 prev 指向結構體本身q_free釋放所有佇列使用到的記憶體空間
container_of 取得節點的位址list_del 斷掉節點與佇列的連結q_release_element 釋放節點使用的記憶體q_insert_head插入新的節點到佇列的第一個節點,且節點中的 value 和 s 字串的內容相同
malloc 取得 element_t 結構體大小的記憶體空間並配置給新的 element_t 結構體指標,若記憶體取得失敗則回傳 falsemalloc 取得 s 字串長度 +1 的記憶體空間並配置給 element_t 結構體指標指向的 element_t 結構體中的 value,若記憶體取得失敗則釋放 element_t 結構體中的 value 並回傳 falsestrncpy 將 s 字串複製到 element_t 結構體中的 value 並在最後加上 '\0'list_add 將新的 element_t 結構體連接到佇列的第一個節點並回傳 trueq_insert_tail插入新的節點到佇列的最後一個節點,且節點中的 value 和 s 字串的內容相同
malloc 取得 element_t 結構體大小的記憶體空間並配置給新的 element_t 結構體指標,若記憶體取得失敗則回傳 falsemalloc 取得 s 字串長度 +1 的記憶體空間並配置給 element_t 結構體指標指向的 element_t 結構體中的 value,若記憶體取得失敗則釋放 element_t 結構體中的 value 並回傳 falsestrncpy 將 s 字串複製到 element_t 結構體中的 value 並在最後加上 '\0'list_add_tail 將新的 element_t 結構體連接到佇列的最後一個節點並回傳 trueq_remove_head將佇列的第一個節點移除,且將節點中的 value 字串複製到字串 sp
container_of 取得佇列第一個節點的位址strncpy 複製節點的 value 字串到字串 sp 並在最後加上 '\0'list_del 斷掉節點與佇列的連結並回傳節點的位址q_remove_tail將佇列的最後一個節點移除,且將節點中的 value 字串複製到字串 sp
container_of 取得佇列最後一個節點的位址strncpy 複製節點的 value 字串到字串 sp 並在最後加上 '\0'list_del 斷掉節點與佇列的連結並回傳節點的位址q_size取得該佇列節點的數量
list_for_each 疊代佇列中所有的節點並加總節點的數量,最後回傳該數量q_delete_mid移除並釋放佇列中間的節點
container_of 找到該節點的位址list_del 斷開節點和佇列的連結q_delete_dup移除並釋放佇列中有相同value的相鄰節點
list_for_each_safe 疊代佇列的所有節點q_swap交換佇列中每兩個節點的位置
list_for_each_safe 疊代佇列中的每個節點,將 node 和 safe 的位置交換q_reverse倒置佇列中的節點
list_for_each_safe 疊代佇列中的所有節點list_move 將每個疊代到的節點移動成佇列的第一個節點q_reverseK將佇列中每 k 個節點視為一個要倒置的 list ,若疊代至最後不足 k 個節點則不進行倒置
list_for_each_safe 疊代佇列中的所有節點list_move 將每個疊代到的節點移動成當前要倒置的 list 的第一個節點q_sort將佇列中的節點依節點的 value 進行升冪或降冪的排序
merge sort
strcmp 比較兩個佇列節點之間的 value 並依此排序與合併,直到合併成一個排序好且完整的佇列q_ascend將節點中的佇列依升冪的順序排序,若是右邊節點的 value 等於左邊節點的 value 則移除並且釋放此節點
q_sort 進行升冪排序list_for_each_safe 疊代排序好的佇列中的節點strcmp 比較是否右邊節點的 value 等於左邊節點的 value ,若是則使用 list_del 斷掉節點和佇列的連結,最後釋放節點所占用的記憶體q_descend將節點中的佇列依降冪的順序排序,若是右邊節點的 value 等於左邊節點的 value 則移除並且釋放此節點
q_sort 進行降冪排序list_for_each_safe 疊代排序好的佇列中的節點strcmp 比較是否右邊節點的 value 等於左邊節點的 value ,若是則使用 list_del 斷掉節點和佇列的連結,最後釋放節點所占用的記憶體q_merge合併所有佇列並且以升冪或降冪排序
strcmp 比較兩個佇列節點之間的 value 並依此排序與合併,直到合併成一個排序好且完整的佇列list_for_each 疊代此佇列的所有節點以此計算節點的總數,最後回傳佇列中節點的總數在 console_init() 中增加 shuffle 命令
增加 do_shuffle 函式對 queue 實現 Fisher–Yates shuffle 演算法
commit 34c176
(1)若能符合分配的要求,「觀察值」與「期望值」的比較就不應差太多,卡方值就不會太大而落入拒絕域
(2)卡方檢定是右尾檢定
(3)卡方檢定是屬於無母數分析技巧
Expectation: 41666
Observation: {'1234': 41583, '1243': 41748, '1324': 41260, '1342': 41425, '1423': 41971, '1432': 41590, '2134': 41414, '2143': 42029, '2314': 41778, '2341': 41921, '2413': 41335, '2431': 41767, '3124': 41561, '3142': 41815, '3214': 41337, '3241': 41656, '3412': 41615, '3421': 41836, '4123': 41881, '4132': 41357, '4213': 41500, '4231': 42002, '4312': 41861, '4321': 41758}
chi square sum: 29.512072193155092
由於有24種可能,因此自由度是24-1=23,且 為 29.512072193155092,27.14<29.512072193155092<32.01,因此由卡方分布表可以得知 P value 落在 0.25~0.1 之間
由於 P value (0.25~0.1)>alpha(0.05),統計檢定的結果不拒絕虛無假說(),也就是樣本資料不拒絕 shuffle 的結果遵循 Uniform distribution,從測試結果的圖表來看也大致符合 Uniform distribution
在測試 Fisher–Yates shuffle 演算法的腳本中 test_count 並不是真正的測試次數,test_count*4 才是真正的測試次數,因此這部分還需要修改
首先看到 constant.h
先將 DUT_FUNCS 定義成四個使用 _ 函式的巨集,再定義 DUT(x) 為展開成 DUT_x 的巨集,最後定義 _(x) 為使用 DUT(x) 的巨集,因此 enum 展開為以下形式
最後取消 _ 巨集
再來看到 fixture.h
先將 _(x) 定義成資料型態為 bool 的 is_x_const(void) 函式,再使用 DUT_FUNCS 巨集定義 is_insert_head_const(void) 、is_insert_tail_const(void) 、 is_remove_head_const(void) 、 is_remove_tail_const(void) , 最後取消 _ 巨集
再來看到 fixture.c
定義 DUT_FUNC_IMPL(op) 為 is_op_const(void) 且返回 test_const(op, DUT(op))。最後定義 _(x) 為使用 DUT_FUNC_IMPL(x) 的巨集,使用 DUT_FUNCS 執行完 _(x) 後取消 _ 巨集
再來看到 test_const 函式
init_once() 初始化 l 並且檢查 ctxs 陣列中每個元素是否都有被初始化
doit 函式
C23 [7.24.3.2] The calloc function
calloc會配置nmemb個元素的陣列,每個元素的大小為size。當無法配置時回傳NULL指標,成功配置時回傳配置記憶體位置的指標。
再來看到 die() 函式
GCC [6.36] Specifying Attributes of Variables
The keyword __attribute__ allows you to specify special attributes of variables or structure fields. This keyword is followed by an attribute specification inside double parentheses. Some attributes are currently defined generically for variables. Other attributes are defined for variables on particular target systems. Other attributes are available for functions (see Function Attributes) and for types (see Type Attributes). Other front ends might define more attributes (see Extensions to the C++ Language).
__attribute__是GCC的一種編譯器指令,後會跟著雙重括號,括號內部會有屬性,而屬性有三種,分別為函式屬性、變數屬性和型別屬性
noreturn 是函式屬性,這個屬性是對編譯器說明此函式沒有回傳的特性,或是因為處理 fatal 的情況所以最後
exit()不會回傳。以die()函式來說,函式內部直接exit(111)所以確定函式不會回傳,所以加了 noreturn 的屬性。標有 noreturn 屬性的函式有可能仍然會以 exception 或 calling longjmp 的方式回傳回 caller ,但不能假設暫存 calling function 的暫存器會被保存下來。
若 noreturn 屬性的函式不是
void是不合理的。
看到 prepare_inputs 函式
其中 randombytes 函式
GCC [4.2.1] Ifdef
The simplest sort of conditional is
This block is called a conditional group. controlled text will be included in the output of the preprocessor if and only if MACRO is defined. We say that the conditional succeeds if MACRO is defined, fails if it is not.
The controlled text inside of a conditional can include preprocessing directives. They are executed only if the conditional succeeds. You can nest conditional groups inside other conditional groups, but they must be completely nested. In other words, ‘#endif’ always matches the nearest ‘#ifdef’ (or ‘#ifndef’, or ‘#if’). Also, you cannot start a conditional group in one file and end it in another.
Even if a conditional fails, the controlled text inside it is still run through initial transformations and tokenization. Therefore, it must all be lexically valid C. Normally the only way this matters is that all comments and string literals inside a failing conditional group must still be properly ended.
The comment following the ‘#endif’ is not required, but it is a good practice if there is a lot of controlled text, because it helps people match the ‘#endif’ to the corresponding ‘#ifdef’. Older programs sometimes put MACRO directly after the ‘#endif’ without enclosing it in a comment. This is invalid code according to the C standard. CPP accepts it with a warning. It never affects which ‘#ifndef’ the ‘#endif’ matches.
Sometimes you wish to use some code if a macro is not defined. You can do this by writing ‘#ifndef’ instead of ‘#ifdef’. One common use of ‘#ifndef’ is to include code only the first time a header file is included. See Once-Only Headers.
依照上面的例子,當
MACRO(也就是巨集)被定義controlled text才會被執行成功,controlled text可以包含前置處理器指示詞,如#define、#elif等等。 Ifdef 也可以組織成巢狀結構,而#endif會和最近的#ifdef(或#ifndef,或#if)組合成一個完整的 conditional group ,但不能跨文件。若條件不成立,
controlled text還是會被轉換和分詞,因此controlled text還是要是合法的 C 語言並且要適當的結束。
#endif不是必要的,但有助於辨別 conditional group 。若是要使用的程式碼中含有未定義的巨集可以善用 Ifdef 結構。
參考 Pre-defined C/C++ Compiler Macros
__linux__ 和 __GNU__ 為 C 和 C++ 編譯器的預定義巨集。預定義巨集可以讓編譯器確認目前的系統是哪種編譯器和作業系統,在寫可攜式軟體時很有用
USE_GLIBC 在以下程式碼中被定義