contributed by < EricccTaiwan
>
在作業開發中,我使用 oh-my-zsh 做為主要 Shell 環境,有許多實用的功能(e.g. zsh-autosuggestions
)和美觀的 UI 界面!
q_new
Commit 7446f52
宣告一個指標 new_qhead
,其型別為 struct list_head *
,並使用 malloc
分配 sizeof(struct list_head)
大小的記憶體空間。
使用 INIT_LIST_HEAD
初始化新分配的記憶體,確保其被正確設置為一個空的鏈結串列。
q_free
Commit 0e45a1f
用 list_for_each_safe
走訪鏈結串列,並依序從鏈結串列中移除和釋放記憶體空間。起初沒有 list_del
下,make test
是沒有問題的 (現在也是沒有問題),當下沒有多想,越往下寫越對於 free
感到疑惑,free
可以在 harness.h
中看到被巨集定義成 test_free
, 點進去看能發現 test_free
其實是在釋放 block_element_t
( malloc
也是分配 block_element_t
),可以看到下方程式碼, test_free
是將欲釋放的 block_element_t
移出其所在的鏈結串列。
但我這邊的疑惑是,free
並沒有將 list_head
節點移出其所在的佇列中, 為什麼能還順利的釋放? TBD…
q_insert_head
& q_remove_head
Commit a4cdde9
使用 malloc
分配大小為 element_t
的記憶體空間,並利用 C 標準函式庫的 strdup
複製字串,將其存入 element_t
的 value
成員中。
接著,透過 INIT_LIST_HEAD
初始化鏈結節點,確保其鏈結狀態正確,最後使用 list_add
或 list_add_tail
,分別將新建立的 element
插入至佇列的首端或尾端。
q_remove_head
& q_remove_tail
Commit 2f3213a
使用 C 標準函式庫的 snprintf
來安全地將刪除的 element_t->value
複製到 sp
,避免緩衝區溢位 (buffer overflow),並確保字串正確終止 (\0
)。
q_size
Commit 4769b2c
利用 list_for_each
走訪鏈結串列並計算節點個數。
q_delete_mid
Commit 551ce22
利用快慢指針找出中間的節點,從其鏈結串列中移除和釋放記憶體空間。
q_delete_dup
Commit 66e7e15
使用 list_for_each_safe
來走訪整個鏈結串列,確保在刪除節點時不會影響迴圈的執行。如果當前節點的value
與下一個節點的value
相同,則刪除當前節點,並標記 dup=true
。在下一次迴圈中,續檢查並刪除所有與該值相同的節點,直到所有相同的節點都被移除。
q_swap
Commit bbf456c
使用 list_for_each_safe
來確保在修改結構時的安全性,並對鏈結串列中的相鄰節點進行兩兩交換。調整指標以維持正確的鏈結結構,確保遍歷時不影響整體鏈結的完整性。
在與[森哥]討論後,我們發現 q_swap
本質上與 q_reverseK(head, 2)
是相同的,當 k=2
時, q_reverseK
也會實現相同的兩兩節點交換效果。然而,目前尚未深入分析 直接實作兩兩交換 與 呼叫 q_reverseK
來達成相同效果之間的優缺點,這部分仍待進一步探討。 TBD…
q_reverse
Commit 12dc9a5
使用 list_for_each_safe
來確保在修改結構時的安全性,並將當前節點的指針 next
和 prev
變換方向,當走訪結束時,再將 head
的 next
和 prev
依序變換方向即可。
同q_swap
,q_reverse
本質上與 q_reverseK(head, q_size(head))
無異,優缺點仍待進一步討論。
q_reverseK
Commit 9cc973e
透過一個 for
迴圈,程式會每次取出 k
個節點進行處理,包括將這些節點從原鏈結串列中切割出來、反轉 (q_reverse
),然後將結果串接至新的頭節點 new_head
。
迴圈中止條件是當迴圈變數 i
超過 size - k
時停止執行,確保每次處理的區間包含 k
節點。當剩餘節點不足 k
個時,不會再執行反轉操作,迴圈就會結束。最終,已反轉的節點會被拼接回原始鏈結串列,完成整個處理流程。
q_sort
, merge_sort
& merge_two_list
Commit 855dffa
起初用quicksort實做,但在進行make test
中發現,q_sort
要求必須為stable sort,因此採用merge sort來實踐,q_sort
會叫輔助函數static void merge_sort()
和static void merge_two_list()
,使用static
是因為這兩個函數僅供q_sort
,為了清楚理解static
的定義,翻閱C99規格書,
… If the declarator or type specifier that declares the identifier appears outside of any block or list of parameters, the identifier has file scope, which terminates at the end of the translation unit. …
If the declaration of a file scope identifier for an object or a function contains the storage-class specifier static, the identifier has internal linkage.
由此可知,static的作用域為文件作用域 (file scope) ,即該標識符 (identifier) 的作用範圍僅限於當前翻譯單元 (translation unit) ,並且內部連結 (internal linkage) 確保該標識符無法被其他翻譯單元引用。
在實做merge_sort
中,透過快慢指針找出中間的節點,使用linux API進行list的切割成,但卻在靜態分析中,遇到了下方的警告,因為對於 const
也是一知半解,所以一樣去翻閱C99規格書
If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.
這段標準表示,當某個變數被 const
修飾後,若透過非 const
型別的左值 (lvalue) 來修改該變數,則行為是未定義的 (undefined behavior)。也就是說,被 const
修飾的變數,只能用來讀取,不能透過非 const
型別的指標來修改其內容。在這段程式碼中,fast
只是用來走訪整個雙向鏈結串列,不需要修改任何list_head
的內容,用const
限制不能修改其值,且因為head->next
的型別為struct list_head *
,因此需要強制轉型。
q_ascend
& q_descend
Commit 56cbacb
實作升冪/降冪排序的想法是,透過從鏈結串列的尾端 head->prev
開始往 head
的方向移動,對於升冪排序,用 minVal
去維持當前的最小值,若當前節點大於 minVal
,將此節點移出鏈結串列並釋放其空間,反之,保留節點並更新 minVal
; 對於降冪排序也是一樣的邏輯,用 maxVal
去維持當前最大值。但卻在靜態分析中,遇到了下方的警告,因為對於 const
也是一知半解,所以一樣去翻閱C99規格書
If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.
這段標準表示,當某個變數被 const
修飾後,若透過非 const
型別的左值 (lvalue) 來修改該變數,則行為是未定義的 (undefined behavior)。也就是說,被 const
修飾的變數,只能用來讀取,不能透過非 const
型別的指標來修改其內容。因此,在這段程式碼中,指針 elem
和 minVal
都不應該被修改,而僅用來讀取鏈結串列的內容,所以它們可以安全地被宣告為 const
,以強化程式的安全性與可讀性。
q_merge
Commit 6268965
因為需要兩兩合併要求出 ceil(size/2))
的值,為了避免使用除法運算,因此下方的程式碼透過位元操作去計算向上取整。
透過 merge sort 的概念,逐步合併兩個佇列的方式,不斷減少佇列的數量,直到只剩下最終合併完成的佇列,最後若處理是否需要反轉
為了避免新增一個函數僅供q_merge
使用,因此調用了先前的 merge_two_list
,但在呼叫時出現了以下的問題,
先回顧q_sort
的程式碼,在 merge_sort
中調用的 merge_two_list
是將 left
和 right
的已排序後的鏈結串列合併到 head
後方,最終 head
就是指向排序後的整個鏈結串列。
由此可知,merge_two_list
的第一個參數應該是目標位置,也就是合併後的結果會存放在哪個鏈結串列裡。因此,在merge_sort
的 while
迴圈中,目標是要把 second->q
的節點合併到 first->q
裡,第一個參數就應該是 first->q
,而不是 head
。
與 charliechiou 討論後,這邊用5組鏈結串列 a, b, c, d, e 作為例子,為了方便解釋,預設其大小已按照升冪排序,( )
表示排序後的鏈結串列,
Commit f4f99dc
運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗
可以新增下方的程式到 ./Makefile
中,若想測試其他的 trace ,把 traces/trace-massif.cmd
改掉就好,最後會用 massif-visualizer 輸出 .massif.out
。
新增 traces/trace-massif.cmd
,因為作業要求提及到 「視覺化 "simulation" 過程中的記憶體使用量」, 因此內容直接複製 trace-17-complexity
來改就好,以下以 q_insert_tail
為例進行分析,
開啟終端機輸入,
輸出如下,
可以看到其中 heap 的使用資源佔用了 86.37 % ,而 test_strdup
佔了其中的 36.97%。
這次把 test_malloc
和 test_free
註解掉,重新測試,
可以看到 heap allocation 的使用量從 87.51% (1,131,446B) 降至 51.01% (334,262B) , 且使用 strdup
僅佔用了 12.14% (79,561B) ,相比 test_strdup
佔用的 36.97% (477,929B) 少了很多, 但也可以發現有 malloc_or_fail
產生,至於原因為何,TBD。
參考 charliechiou
在 console.c 中 do_web()
是一個開啟 Web 伺服器的函式,參考作業說明 lab0 (C) 的操作,開啟兩個終端機
在終端機 2 中輸入 curl http://localhost:9999/new
, 可以預期在終端機 1 呼叫 q_new()
,此時打開瀏覽器輸入 http://localhost:9999/new
,卻在終端機 1 中看到下方的錯誤訊息,
favicon.ico
Commit f2e261d
為了解決 favicon.ico
所產生的問題 ,根據老師的步驟修改,同時我加上了 "<h1>Good Job, Eric!</h1>"
,可以預期成功用瀏覽器 request 後,應該要出現 Good Job, Eric! 的字眼。
修正後,重新在瀏覽器輸入 curl http://localhost:9999/new
,便能成功透過瀏覽器發出 request 和見到下方的字樣。 仔細觀察終端機的輸出,Chrome 發送了兩次 request ,因此可以看到終端機輸出了兩次的l = []
,至於如何解決此問題, TBD 。
Commit 40d8775
此時,在先前的終端機 2 中輸入 curl http://localhost:9999/new
,會顯示一長串的 html 檔案的內容。
可以由此判斷,網頁伺服器無法判斷 request 的來源,可以在 parse_request
中,把透過接收的 request 用終端機輸出,如下,
透過 request 的資訊可以看出 User-Agent 的差異,我們能以此判斷 request 是否來自 curl , 可以注意到我只有使用 Chrome 發出 request ,但回傳了一長串的 User-Agent
,其中的關鍵字包含 Mozilla 和 Safari ,即是 User agent spoofing ,簡言之 Chrome 的 User-Agent
包含了 Mozilla 和 Safari 是歷史相容性需求,同時瀏覽器可以透過 User-Agent
欺騙老舊的網站,讓老舊網站認為 Chrome 是支援的瀏覽器; 但好像沒法判別是 Mozilla 或是 Chrome 所發出的,益或是也無此必要性,TBD…
在 parse_request
儲存將 User-Agent 的資訊,
並在 web_recv
中判斷 user_agent 中的資訊是否包含 "curl"
,
最後於 web_eventmux
中對於判斷 request 來源,進行了下方的修改,
最後用終端機來驗證,如下圖呈現,但對於瀏覽器為何會發出兩次的 reqeust , TBD 。
User-Agent Switcher and Manager 的擴充功能,可以任意的切換 User-Agent 內容,因此我在 Chrome 中,輸入 curl 的 User-Agent string,或是在原本的 User-Agent 後方加上 "Curl" ,都可以看到下方的結果。
User-Agent spoofing 的優點已在上述提及,至於會有什麼樣的問題? TBD 。
select()
解釋 select 系統呼叫在本程式的使用方式,並分析 console.c 的實作,說明其中運用 CS:APP RIO 套件 的原理和考量點。可對照參考 CS:APP 第 10 章重點提示
上面的 code 改完後,才發現並沒有詳讀 select
, 參考 Integrate linenoise with web server #162 ,看到下方的程式碼片段,可以理解 web_eventmux()
在處理來自伺服器和終端機的 mutex 問題,因此查閱了 select(2),先去理解各個 interface 的用意。
select()
allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.
select()
允許程式 同時監聽多個檔案描述符(file descriptors, FDs),並在其中至少有一個變為「可讀」、「可寫」或發生異常時返回,避免程式進入 不必要的阻塞狀態。
fd_set
A structure type that can represent a set of file descriptors. According to POSIX, the maximum number of file descriptors in an fd_set structure is the value of the macro FD_SETSIZE.
fd_set
是用來存放文件描述 (fd, file descriptors)集合的結構體,通常用在 select()
中,來監聽多個 fd 的狀態。 line 3 即透過 fd_set
宣告 listenset
。
FD_ZERO()
This macro clears (removes all file descriptors from) set. It should be employed as the first step in initializing a file descriptor set.
FD_ZERO()
是一個用來初始化 fd_set
的巨集,作用是清空 fd_set
, line 4 便是初始化 listenset
。
FD_SET()
This macro adds the file descriptor fd to set. Adding a file descriptor that is already present in the set is a no-op, and does not produce an error.
FD_SET()
是用來將指定的FD加入 fd_set
的巨集,如果 line 5 就是將標準輸入(STDIN
)加入 fd_set
,讓 select()
監聽它是否可讀; 如果 server_fd
存在,江 server_fd
加入 fd_set
。 max_fd
則是計算所有 FD 的最大值。
FD_ISSET()
select()
modifies the contents of the sets according to the rules described below. After calling select(), theFD_ISSET()
macro can be used to test if a file descriptor is still present in a set.FD_ISSET()
returns nonzero if the file descriptor fd is present in set, and zero if it is not.
用來檢查 fd_set
內某個 FD 是否還在 select()
返回的結果中。
FD_CLR
This macro removes the file descriptor fd from set. Removing a file descriptor that is not present in the set is a no-op, and does not produce an error.
FD_SET()
是從 fd_set
來移除指定的FD的巨集,讓 select()
不再監聽。
line 17 if (server_fd > 0 && FD_ISSET(server_fd, &listenset))
檢查 server_fd
是否有新的連線,如果有,用 FD_CLR
先移除,避免影響下次的 select()
, 接著就是使用上面新增的 is_curl
來判斷此連線的來源。
可對照參考 CS:APP 第 10 章重點提示
console.c
的目的為實做 command line interface (CLI) ,允許從標準輸入 stdin
或網路 request 接收指令。
其中 select()
負責同時監聽標準輸入 (STDIN_FILENO
) 和網路請求 (web_fd
),以及當其中一個 FD 可讀時,執行對應的處理。
cmd_select()
在 cmd_select()
中,當 stdin
有輸入時,系統會執行 readline()
來讀取輸入內容,然後交由 interpret_cmd()
解析並執行相應的命令。然而,傳統 readline()
每次讀取輸入時可能會頻繁呼叫 read()
,這會導致效能低下,特別是當輸入來源是檔案或 socket 時,每個字元都可能觸發 read()
系統呼叫,使 I/O 成本大幅增加。因此,為了提升效能並減少 read()
次數,console.c
引入 RIO(Robust I/O)緩衝機制來優化 readline()
,透過 rio_read()
來管理讀取流程。
readline()
當 buf_stack->count == 0
時,readline()
才會執行 read()
:
* 一次性讀取 8192 bytes (RIO_BUFSIZE
)。
* 之後的讀取直接來自 buffer,不再執行 read()
,直到 buffer 耗盡。
這樣避免了每讀取一個字元就調用 read(),提升效能。
如果 readline()
讀到 EOF,它會從 buf_stack
內彈出 (pop) 上一層 buffer,這讓 readline()
可以讀取不同來源的輸入,而不會頻繁執行 read()
。
Commit02d154d
參考 willwillhi
參考作業說明 lab0 (D) 實做 q_shuffle
和 willwillhi 對於 queue.h
和 qtest.c
的修改, 因為 queue.h
是不能修改,因此在下方做上註記。
Commit 8484015
參考作業說明 lab0 (D) 新增測試腳本 test_shuffle.py
。
參考lab0 (D)
補充:
test_shuffle.py
會跑有點久,平均一次 13 分鐘左右
觀察到的頻率 | 期待的頻率 | ||
---|---|---|---|
[1,2,3,4] | 41665 | 41666 | 0.0000240004 |
[1,2,4,3] | 41891 | 41666 | 1.2150194400 |
[1,3,2,4] | 41722 | 41666 | 0.0752652042 |
[1,3,4,2] | 41489 | 41666 | 0.7519080305 |
[1,4,2,3] | 41649 | 41666 | 0.0069361110 |
[1,4,3,2] | 41608 | 41666 | 0.0807372918 |
[2,1,3,4] | 41424 | 41666 | 1.4055584890 |
[2,1,4,3] | 41662 | 41666 | 0.0003840061 |
[2,3,1,4] | 41396 | 41666 | 1.7496279940 |
[2,3,4,1] | 41575 | 41666 | 0.1987471800 |
[2,4,1,3] | 41655 | 41666 | 0.0029040465 |
[2,4,3,1] | 41365 | 41666 | 2.1744587910 |
[3,1,2,4] | 41986 | 41666 | 2.4576393220 |
[3,1,4,2] | 41774 | 41666 | 0.2799404790 |
[3,2,1,4] | 41848 | 41666 | 0.7949887198 |
[3,2,4,1] | 41730 | 41666 | 0.0983055729 |
[3,4,1,2] | 42060 | 41666 | 3.7257236120 |
[3,4,2,1] | 41784 | 41666 | 0.3341813469 |
[4,1,2,3] | 41765 | 41666 | 0.2352277636 |
[4,1,3,2] | 41240 | 41666 | 4.3554936880 |
[4,2,1,3] | 41498 | 41666 | 0.6773868382 |
[4,2,3,1] | 41732 | 41666 | 0.1045456727 |
[4,3,1,2] | 41900 | 41666 | 1.3141650270 |
[4,3,2,1] | 41582 | 41666 | 0.1693467095 |
Total | 22.20851533624538 |
= 22.20851533624538
本次實驗的排列組合有 = 24種,所以自由度是 23,透過查尋卡方分布表,可以查到 P value 位於 0.1 ~ 0.9 的區間,因為 P value(0.1 ~ 0.9) > alpha (0.05) ,統計檢定的結果不拒絕虛無假說 () 也就是樣本資料不拒絕 shuffle 的結果遵循 Uniform distribution,因為沒有足夠證據推翻,可以看到下方的圖片, shuffle 的頻率是大致符合 Uniform distribution 的。
包含以下性質 : Unpredictability, Uniformity, Independence, 和 High Entropy
用
隨機(randomness)是一種無法預測的現象,通常具有以下關鍵特性:
透過 來測亂度。
安裝 ent,此工具 ent
可計算一輸入字串的Entropy。
根據作業說明,嘗試計算 linux 內建的 PRNG /dev/random
可以看到,因為 /dev/random
的內容隨機,因此每一個位元的資料量較大,同時 1 byte char 的大小為 ,所以最大的 entropy 是 ,對照上方的 Entropy = 7.999982 bits per byte.
,與理論值無異。
在 ./qtest 中執行 option entropy 1
並搭配 it RAND 10
來計算每個字串的 shannon entropy ,其輸出結果如下
這些字串的熵是透過 shannon_entropy.c
所計算的,因此查看程式碼,
這邊的計算遵循著先前計算 的公式,並正規化輸出。
針對第一筆 wewgqrne(29.69%)
進行分析,
char | times | p(x) | ||
---|---|---|---|---|
w | 2 | 2/8 = 0.25 | -2 | 0.5 |
e | 2 | 2/8 = 0.25 | -2 | 0.5 |
g | 1 | 1/8 = 0.125 | -3 | 0.375 |
q | 1 | 1/8 = 0.125 | -3 | 0.375 |
r | 1 | 1/8 = 0.125 | -3 | 0.375 |
n | 1 | 1/8 = 0.125 | -3 | 0.375 |
2.5 |
,計算 1byte 編碼下的 entropy = ,實際值只有 的誤差。
作者提出 dudect 工具,用於檢測程式碼的時間複雜度是否為 constant time 。論文中 Step3: Apply statistical test 是本次實做的關鍵,透過量測程式在兩筆不同輸入(如 fix-vs-random )時的執行時間分佈,並應用 Welch’s t-test來判斷分佈差異,試圖推翻「兩個時間分佈相等」的虛無假設 (null hypothesis, )。
虛無假設 (null hypothesis, )
論文將此解釋為 "the two timing distributions are equal" ,若兩組測資的執行時間分佈是相等的,更精準的說若兩個樣本的變異數相等,代表程式碼的時間複雜度即是 constant-time。
Student’s t-test 假設兩個樣本均值來自正態分佈(normally distributed)的母體,並且母體的變異數相等。而 Welch’s t-test 是 Student’s t-test 的變體,適用於變異數不等的情況,但仍然保持正態分佈的假設。因此,本作業測試 constant time implementation 時,使用的是 Welch's t-test,而非 Student’s t-test。
Commit 709516f
可以在 ./qtest.c
中找到 simulation , 當值為 1 時會檢查 queue_insert
和 queue_remove
是否符合 constant time 。
先從 queue_insert
看起,可以發現 is_insert_tail_const()
是判斷是否符合 const time 的函式,用 ctrl + 滑鼠左鍵此函式後,會跳到 ./dudect/fixture.h
,但卻找不到 is_insert_tail_const()
的介面,觀察到了下方的註解, /* Interface to test if function is constant */
,可以預期是透過 #define _(x) bool is_##x##_const(void);
去定義剛剛找不到的介面。
再點開上方的DUT_FUNCS
會跳到./dudect/constant.h
,
首先,因為 fixture.h
中有 #include "constant.h"
,所以預處理器 (preprocessor) 會先展開 constant.h
的巨集,接著才是 fixture.h
,
到這邊開始看不懂了,為了看懂需要釐清兩個觀念:巨集的展開,和 ##
的用意。
是時候來查閱規格書了,首先我看不懂 #define _(x) bool is_##x##_const(void);
的用意?
A preprocessing directive of the form
# define identifier lparen identifier-listopt ) replacement-list new-line
# define identifier lparen … ) replacement-list new-line
# define identifier lparen identifier-list , … ) replacement-list new-line
defines a function-like macro with parameters, whose use is similar syntactically to a
function call.
由規格書可以理解這條巨集的格式如下,
其實這條巨集和 #define ADD(a,b) a+b
沒有差別,只是第一次看到 identifier
是 _
(底線),原地嚇到。
##
operator參考 Linux 核心原始程式碼巨集: max, min ,##
在 C 語言前置處理器的作用是 concatenation (即連結、接續的意涵)。
If, in the replacement list of a function-like macro, a parameter is immediately preceded or followed by a
##
preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence;
This is called token pasting or token concatenation. The
##
preprocessing operator performs token pasting. When a macro is expanded, the two tokens on either side of each##
operator are combined into a single token, which then replaces the ‘##’ and the two original tokens in the macro expansion.
根據 C99 規範,當 function-like macro 的參數前後緊鄰 ##
運算子時,該參數會被對應的引數替換,並與 ##
兩側的標識符連結。因此,在 #define _(x) bool is_##x##_const(void);
中 x
會被 arugment 取代,而 is_
和 _const(void);
會分別連結到其前後,最終形成有效的函式宣告。例如,當傳入 insert_head
時,展開結果將為 bool is_insert_head_const(void);
。
首先在 line 1 的 DUT_FUNCS
會被展開成
展開 line 8 的 DUT(x)
巨集,到此 line 為止還沒有使用到此巨集。
展開 line 11 的 #define _(x) DUT(x),
巨集,會變成
line 11 的 DUT(x)
又會被 line 8 的巨集 #define DUT(x) DUT_##x
展開,
line 12 取消 _
的定義。
了解至此,把上述程式碼複製到 test.c
對於巨集依序進行測試,並輸入 $ gcc -E -P test.c -o output.c
,便可以在 output.c
中看到預處理後的結果,最後 enum
的成員如下,
現在回到 dudect/fixture.h
中,
#define _(x) bool is_##x##_const(void);
實際上定義了四個函式原型
在 dudect/fixture.c
中,
則定義了函式的實作
參考作者在 update_statistics
中 discard the first few measurements ,因此做了以下更動
觀察 dudect_main
中,在呼叫 update_statics
前會先呼叫 prepare_percentile
,先對測量到的執行時間數據進行預處理,以確保統計分析的準確性。我認為這樣的處理就是論文中的 Step2. Apply post-processing ,此步驟實做去掉某些極端值 (Cropping) 和 High-order preprocessing。因此將採用作者的原始碼,新增以下段落,並針對 fixture.c
進行了對應的修改。
做完改動後,就可以在 action 首次看到卡比了。
TBD…
用表格紀錄我最一開始的理解,下方的表格對應 list_sort
程式碼中的 do-while 迴圈 (line 4~27) , line 9 判斷當前的 count
的 MSB 和 LSB 中,只要有 1 個 0-bit, line 12 就會對於 pending
進行 merge, line 21 - 26 再把節點從 list
拉一個到 pending
上,直到 list
上沒有節點,同時也結束 合併:不合併 = 2:1
的原則。
表格為了輸出美觀 ,因此省了一些字
#
代表 節點總數
;count 二進位
欄位中,pending上 有 個節點,省略了 pending 上
[ ]
代表欲合併的數個節點//
代表合併後,pending 新增的節點,# | count 變化 | count 二進位 | merge | pending 上的節點 |
---|---|---|---|---|
1 | 0 1 | 0000 0001 | no() | 1 |
2 | 1 2 | 0001 0010 | no() | 1 1 |
2+1 | (過程) | 有 個節點 合併前 個節點 |
[1* 1*] // 1* | |
3 | 2 3 | 0010 0011 | yes | (2) 1 |
4 | 3 4 | 0011 0100 | no() | 2 1 1 |
4+1 | (過程) | 有 個節點 合併前 |
2 [1* 1*] // 1* | |
5 | 4 5 | 0100 0101 | yes | 2 (2) 1 |
5+1 | (過程) | 有 個節點 (一個 2*, 一個2*, 一個(1',1')) 合併前 個節點 |
[2* 2*] 1' // 1' | |
6 | 5 6 | 0101 0110 | yes | (4) 1 1 |
6+1 | (過程) | 有 個節點 合併前 個節點 |
4 [1* 1*] // 1* | |
7 | 6 7 | 0110 0111 | yes | 4 (2) 1 |
截至目前,對於這種合併方式有個疑問,以7個節點為例,操作完 do-loop 後, pending
明顯沒合併完成阿,再跟 charliechiou 討論並詳閱 list_sort
後,才發現 line 30 - 39 就是從尾端把部份合併完成的 pending 上的每個節點,兩兩進行合併,直到全數合併完, line 41 再建構回原本的雙向鏈結串列。
至此,也終於是稿懂了 list_sort
的程式碼了,因此就將 list_sort
, merge
和 merge_final
做了簡單的修改,補進去 queue.c
中,就能執行了。
雖然只是很小的 debug ,但很開心能貢獻程式碼。
測試時發現 it test -10
竟然沒有報錯,原來在 qtest.c
中,只會判斷 reps 是否為整數,所以負數和 0 都能通過檢查,雖然不影響程式運行,但對於使用者不太直觀。
因此補進了負數和 0 的除錯判斷後,
再次進行測試,便能正常報錯了,提交的 PR 已被 merge !
這次的 patch 與 charliechiou 一同協作
這邊做個實驗 ,開啟 ./qtest
後連續輸入 5 次的 unkown command ,
這次的 bug 是接續上面的測試後,意外發現的,在原本的 console.c
中,當輸入的錯誤超過了錯誤上限 err_limit
,會讓設 quit_flag = true
,這會導致 interpret_cmd
因為判斷到 quit_flag == True
直接返回 flase
。
簡單來說,一旦錯誤輸入達到了上限,任何 command 都不再允許,包括 quit
,只能輸入 CTRL + c
強制離開,這會導致可能的記憶體洩漏,起初我們做了以下的改動,預先宣告 do_quit
的函式,就可以再不更動程式的結構下,讓 record_error
呼叫,一旦輸入錯誤超過錯誤上限,便強制 quit
。
後來便收到了 jserv 老師的回覆,根據此回覆我們引入了一個 helper function force_quit
給 do_quit
和 record_error
使用,
經過這樣的更動,當使用者輸入的錯誤指令超越 err_limit
時, record_error()
會直接call force_quit
強制終止,此 PR 也已經被 merge !
Before: Ambiguous log command feedback
After: Clear and informative log command messages
Before: Ambiguous log command feedback
After: Clear and informative log command messages
更改前
更改後