contributed by < Shiritai
>
Urbaner
以一個主要個開發線或者主題來敘述開發是很好的想法,未來我將嘗試這樣做。
不過 Hackmd 本身提供快速移動到某標題的功能 (table of content),以快速閱覽任意長度且設置正常標題的筆記,故似乎沒有必要使用開關標籤。
我認為開關標籤的使用是省略可選的內容,比如本筆記內隱藏針對 VSCode 中使用註解格式的議題,這是因為此部分僅與使用 VSCode 的使用者有關,且並不直接與開發的邏輯有關。
Eroiko
ok
Eroiko
建立新鏈結串列節點,僅在成功配置記憶體時初始化回傳值 ret
為首位節點,並回傳。若配置失敗,ret == NULL
,正好符合函式要求。
留意資訊科技詞彙翻譯,變更下方用語。
ok,本以為真的走訪全部可稱遍歷。
今後會多加注意名詞的使用。 Eroiko
透過 list_for_each_entry_safe
巨集走訪所有元素節點,分別先釋放 value
成員再釋放元素節點本身。使用 _safe
版巨集走訪是因為一路上的節點會被釋放。最後不忘要釋放首位元素節點。
Allocate 翻為配置。
Eroiko
由於 q_insert_head
和 q_insert_tail
的邏輯相似,皆需「配置新元素節點並複製給定字串值」,故將此邏輯獨立為本函式。我定義回傳值為指標,非 NULL
表成功,否則表 element_t
或 element_t::value
配置失敗。
注意以下四點:
n_ele
配置成功但 n_ele->value
失敗,則回傳 NULL
前不忘歸還 n_ele
。static
是為了縮小可見度 (已修改
Eroiko
static
是為了 visibility,而非「封閉性」,後者是數學用語。
確實… Eroiko
辛辛苦苦使用 Linux Kernel-doc 的註解不能即時得到回饋。
如果讀者您知道哪些 VSCode 擴充套件可以解析 Linux Kernel-doc 的,請務必告訴我。
使用 doxygen 可以得到關鍵字的辨識與參數提示。
一開始使用最普通的實作。由於都有「配置新元素節點並複製給定字串值」,故將該邏輯獨立為 q_element_alloc
。最後使用 list_add_tail
巨集幫助完成 push front/back 的邏輯。
會發現這倆函式行為極其相似,僅差在函式名稱以及插入位置的不同。不妨使用前處理器的技巧,讓程式更好維護,如下。
注意到前處理的換行不能被 "//" 屏蔽,故使用 C 風格的註解。
Eroiko
如此,兩函式的實作變為簡單兩行。
首先處理邊界情況,接著取得串列的首位準備移除,當 sp
合法時複製欲移除節點之值,最後回傳。
與插入的情況相同,可以發現這倆函式行為極其相似,僅差在函式名稱以及插入位置的不同。不仿再次使用前處理器的技巧,讓程式更好維護,如下。
注意字串的複製我使用 memcpy
來加快效率,並在最後補上 \0
。
於是兩刪除節點函式的實作變成:
減少多餘的程式碼與人工同步的成本。
注意用語!在電腦科學和工程中,冗餘 (英語: redundancy) 是指系統為了提昇其可靠度,刻意組態重複的零件或是機能。顯然這裡不是 redundancy 的寓意,應該避免該詞彙的使用。
ok Eroiko
如 Spec 給我們的提示,遍歷整個鏈結串列得所求大小。
有鑒於「尋找中點」需求不僅應用於一處,我將此邏輯獨立為本靜態函式。注意第二個參數 offset
可用來儲存「中點」實際上自 head
往 next
方向之距離,不浪費用來存取非連續記憶體的辛勞;若offset == NULL
則忽視此參數。相似的,文件註解採 doxygen 格式。
算法是利用雙向鏈結串列的特性,以兩個指標分別向 next
和 prev
方向等速移動,透過 front
向 next
、back
向 prev
遍歷,目標是讓 front
最終指向目標。由於每次各走一步,總位移量為 ,故有兩種相遇的可能:
back
和 front
最接近時差一,表有偶數個節點,此情況必有一次迴圈中 back->prev == front
。back
和 front
會相遇,表有奇數個節點。用以上兩情況決定迴圈的持續條件。
利用 q_find_mid
,根據以上找到欲刪除節點後,便是正常的刪除操作。
要刪除重複的節點,首先列表至少要有兩元素,故邊界條件額外考慮僅單節點的情況,可使用 list_is_singlar
巨集來判斷。
接下來的演算法為:透過比較鄰近兩元素 (cur
, nxt
) 的字串決定行為。
若字串相同,則
cur
mark_del
標記 nxt
為「待刪除元素」若不同且標記不為空,則
nxt
節點的走訪可搭配運用 list_for_each_entry_safe
巨集。
由於後面會實作 q_reverseK
函式,而 q_swap
實際上就是 q_reverseK
的特例,故直接複用即可,這使程式更加易於維護。
利用巨集,可透過遍歷所有節點,並將節點的 list_head::next
和 list_head::prev
交換,即表示反轉串列,故以下為第一種實作。
特例是要另外反轉 head
的兩個指標。若要消除此例外,可以使用 do-while
迴圈取代,便是第二種實作如下。
考慮邊界條件,只要 head
存在,整套算法即可正常運行。
本函式乍一看與 q_reverse
的運作邏輯相似,就是複雜了一點。每 k 個元素一群,要滿 k 個元素才可以進行反轉,故不能邊遍歷邊反轉,得先確認眼下真有 k 個元素。復用 q_reverse
得到的演算法可以是:
list_cut_position
,其根據給定之 head
和右界lead
向前遍歷 k 位後確認要截斷的位置。lead
的起點不可為 head
,因為 list_cut_position
的截斷是「包含」給定之右界: ,範圍是左開右閉。lead
,將其初始化為 head->next
,截斷之右界也順應變成 lead->prev
。rev_head
。q_reverse
反轉暫存串列。new_head
。head
串列。與 q_reverse
相似,考慮邊界條件,只要 head
存在,整套算法即可正常運行。
由於 Merge request #133 加入對升序、降序的支援,我決定將「是否正確的排序」的邏輯從原本呼叫 strcmp
後比對回傳值的操作獨立成 in_order
函式,以便多處重複利用。
實作的部分即儲存 strcmp
的結果,對三種可能進行確認,詳見函式內註解。
另由於本函式小巧玲瓏,故加入 inline
修飾,期待編譯器將呼叫處改為程式碼展開。
本輔助函式的意義在將 sub
串列併入 major
串列中,假設兩者都排完序。不同於上課介紹過的「先找一暫存串列,將兩目標串列的節點併入暫存串列後轉移回原串列」的做法,本函式假設一定併入 major
,故可以省略與 major
長度相等之「併入」操作,降低執行時的負擔。
原本的實作為以下:
除了 edge case 有點雜亂外, do-while
迴圈內也不怎麼「美觀」,函式的其中一個返回的可能甚至在 else
條件下,實在說不上有品味…
經重構後為以下,邏輯相等不過好看多了。
新的 merge_two_lists
應該提供升/降序的合併,故將比較部分從
改為
注意新的函式簽名額外加入 descend
參數。
考慮到後面我們要實作穩定的排序,不一定要使用 merge sort,insertion sort 也是某些情境下的好選擇。
在資料結構中我們學到,在欲排序元素較少的情況下,尤其趨近排序完成的情況下,insertion sort
擁有優異的效能表現,無論是時間抑或空間複雜度上。
時間上可使用迴圈完成,無需 function call 的額外負擔。具體而言可能有參數的預備、calling stack 的操作 push/pop,code memory 中的跳導致的 cache miss 等。
空間上 insertion sort 可以就地排序,且僅需 的額外空間,比起 merge sort 使用 calling stack、quick sort 使用 calling stack 或普通 stack 還好。
注意到我的演算法參數有二,標示著欲排序的範圍。不直接如 q_sort
傳入 head
是為了擴展性,是為了讓本函式可以進行局部排序。
順帶一提,一開始實作時由於不是先以 l_bd
(left bound) 和 r_bd
(right bound) 兩恆定指標將我們感興趣的邊界記下,而是動態去與 from
、to
的鄰居比較,未考量 from
、to
本身也是會被移動的對象,所以除錯搞了很久…
重構時為邊界加上 const
修飾。
新的 q_i_sort
應該提供升/降序的合併,故將比較部分從
改為
注意新的函式簽名額外加入 descend
參數。
本函式為 merge sort + insertion sort 的混合穩定排序。當欲排序數量大於 THRESHOLD
時使用 merge sort 的 partition,反之使用前面實作好的 insertion sort q_i_sort
直接排序。最後透過同樣實作好的 merge_two_lists
完成合併。
程式碼可分為三塊解釋。
邊界條件與變數初始化
THRESHOLD
的大小我認為與 insertion sort 的甜蜜點和 cache 的大小有關。
另外注意到之所以我不將單節點串列納入邊界情況,是因為我的實作確保以下兩點:
如此,遇到長度很低的串列自然會使用 insertion sort 排序完成,省略了大量函式呼叫。
切分與遞迴
中點的尋找我採用前面實作過的 q_find_mid
來同時取得分割點與此點兩邊串列大致的長度,後續將以此決定切分後的邏輯。
合併
以 head
為標的合併。
新的 q_sort
函式簽名加入 descend
參數,連帶調整呼叫 q_sort
, q_i_sort
和 merge_two_lists
。
解決 q_descend
的演算法為將題意換句話說,就是維護從右往左看的單調上升序列。
由於新版 lab0-c
改成要實作 q_ascend
和 q_descend
兩個維護單調序列的函式,此兩函式實際上唯一的區別在輔助函式 in_order
的回傳值。如同 q_insert_head 和 q_insert_tail 的實作與重構,我以 gen_q_monotonic
巨集實現兩函式,透過巨集參數決定演算法維護的是單調升序抑或降序的序列。
與舊版的 q_descend
相比,調換 if-else
的內容,使「升序」(descend
替換為 false
) 為預設的序列,這符合程式的直觀行為。
如此一來,q_ascend
和 q_descend
的實作變成簡單兩行。
當中 為總元素數。
要將所有 queue 的內容併為一個,相當於不斷套用前面實作好的「將一串列併入某串列」的邏輯。要使 merge_two_lists
正常運作的條件為 major
參數需非 NULL
,故將等價意義的 !head || list_empty(head)
設為邊界條件。
過程為先將首個 queue 元素 first
自原串列分離,遍歷所有剩下的串列並將當中的 queue 一個個併入 first->q
,最後把 first
接回元素串列。
新的 q_merge
應該提供升/降序的合併,故將比較部分從
改為
注意新的函式簽名額外加入 descend
參數。
為了更好的閱讀 Valgrind 的輸出報告,可以使用命令列的重新導向功能,比如
若要將 stdout
和 stderr
重新導向至同一檔案,可改成以下
如此一來我們便能仔細端詳 stdout
和 stderr
。
在 q_remove_head
和 q_remove_tail
中,我原本認為使用 memcpy
理當較 strncpy
高效,故使用前者。不過開啟 Valgrind 後發現 memcpy
會讀取到非法位置。
TODO
以下為小筆記
Linux 核心 list_sort.c 引用關係表,省略所有名為 linux
的路徑。
TODO
以下為小筆記
select
資訊
以下資訊來自 Linux Manual Page。
select
函式功能
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.
關於 File descriptor sets
The principal arguments of select() are three "sets" of file descriptors (declared with the type fd_set), which allow the caller to wait for three classes of events on the specified set of file descriptors. Each of the fd_set arguments may be specified as NULL if no file descriptors are to be watched for the corresponding class of events.
注意官方提到若使用於迴圈內,也就是 fd_set
可能在迴圈內被修改,則我們應該 (用 FD_ZERO
) 重新初始化 fd_set
。
以下四個巨集協助修改 file descriptor set。
FD_ZERO
: 初始化 fd_set
。FD_SET
: 加入某 fd
至 fd_set
。FD_CLR
: 將某 fd
自 fd_set
移除。FD_ISSET
: 確認某 fd
是否在 fd_set
中,存在則非零,不存在則為零。另一函式 pselect
與 select
有些許不同。
加入 select
於 linenoice:line_edit
中加入 Spec 建議的程式碼後,須引入以下標頭檔。
並修改 listenfd
為 web 輸入的檔案描述子 l.ifd
、將 SA
縮寫改為 struct sockaddr_in
。
queue.[ch]
和連帶的檔案,測試後用 Git 管理各項修改,要能滿足 $ make test
自動評分系統的所有項目。
lab0-c
專案,比較你自己實作的 merge sort 和 Linux 核心程式碼之間效能落差qtest
提供的 web
命令得以搭配上述佇列實作使用,目前一旦網頁伺服器啟動後,原本處理命令列操作的 linenoise
套件就無法使用,請提出改善機制
qtest
提供新的命令 shuffle
,允許藉由 Fisher–Yates shuffle 演算法,對佇列中所有節點進行洗牌 (shuffle) 操作,需要以統計的原理來分析你的實作,探究洗牌的「亂度」qtest
中執行 option entropy 1
並搭配 ih RAND 10
一類的命令,觀察亂數字串的分布,並運用「資訊與熵互補,資訊就是負熵」的觀念,設計一組新的命令或選項,得以在 qtest
切換不同的 PRNG 的實作 (可選定 Xorshift),搭配統計分析工具,比較亂數產生器 (如 Xorshift vs. 內建) 的品質
qtest
執行過程中的錯誤qtest
實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗if-else
採 K&R 風格
帶參數除錯
使用重新導向除錯
常用命令
Command | Alias | Argument | Usage |
---|---|---|---|
help |
h |
ALL_GDB_COMMAND |
啥都不知道時先 help 一下就對了 |
print |
p |
SOMETHING |
印出 SOMETHING ,可以是參數、指標,甚至呼叫函式的節果 |
break |
b |
FILE:LINE_NUMBER |
在 FILE 的第 LINE_NUMBER 設定中斷點,比如 b queue.c:123 |
step |
- | - | 單步執行 |
continue |
c |
- | 從當前中斷點繼續執行 (至下個中斷點或執行結束 or 報錯) |
info |
i |
SOME_GDB_COMMAND |
印出 SOME_GDB_COMMAND 的相關訊息,可使用 help info 查看哪些命令可供查看 |
backtrace |
bt |
- | 印出 function stack |
frame |
f |
FRAME_NUMBER |
移動至 function stack 中編號 FRAME_NUMBER 的 frame,目前有哪些 frame 可用 bt 查詢 |
up |
- | - | 切換到上層 frame |
down |
- | - | 切換到下層 frame |
查閱教育部辭典「幀」的釋義: 量詞。計算照片、字畫等的單位。但這樣的量詞跟 GDB 原本的 stack frame 不相符,後者更在意可切換 stack context 的範疇。因應漢語的侷限,這裡不該翻譯。
ok
malloc
, calloc
透過巨集與 test_malloc
, test_calloc
掛鉤。由於我 fork 的 repository 與源 repository 有不少落差,今希望將源 repository (i.e. upstream
) 的修改也加入我 fork 的 repository。
upstream
upstream
upstream
backup
upstream
合併
使用 git 自動合併的話,可以看到數個檔案出現衝突,這符合預期,接下來便是針對衝突逐一解決。q_sort
的部分,由於改成支援升序與降序兩種方式,q_sort
的簽名進行了調整,需合併衝突:採用新簽名。q_ascend
函式,直接併入即可。ssh-private-key
問題
main.yml
使 github action 可運行測試。
當然這顯然不是個好方法,僅權宜
upstream
上同學的 merge request 併入後得到解決。repost.c:report_event
const
解決。upstream
的 commit 0180045 將 pre-commit.hook
修改,我將其 sync 回我 fork 的 repository 而解決。queue_init
不存在,應為 q_init
sigsegvhandler
不存在,應為 sigsegv_handler
sigalrmhandler
不存在,應為 sigalrm_handler
請修改 HackMD 頁面。
好的,修改完畢。
web
linenoiseEdit
可改為 linenoice.c:line_edit
方能立刻找到*
無序列表或 1.
有序列表) 幾乎都沒有縮排,也許是為了控制單行字數能保持一致,不過有點違和。