# 2025q1 Homework1 (lab0) contributed by < `ihost1002` > {%hackmd NrmQUGbRQWemgwPfhzXj6g %} ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64- bit Address sizes: 39 bits phy sical, 48 b its virtual Byte Order: Little Endi an CPU(s): 6 On-line CPU(s) list: 0-5 Vendor ID: GenuineInte l Model name: Intel(R) Co re(TM) i5-8 400 CPU @ 2 .80GHz CPU family: 6 Model: 158 Thread(s) per core: 1 Core(s) per socket: 6 Socket(s): 1 Stepping: 10 CPU(s) scaling MHz: 20% CPU max MHz: 4000.0000 CPU min MHz: 800.0000 BogoMIPS: 5599.85 ``` 註: Ubuntu使用的是 24.04.2-LTS ## 背景知識 ### C 語言規格書 C99 ( `ISO/IEC 9899:1999` ): pdf 版本: [N1256](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) 網頁版本: [N1256](https://port70.net/~nsz/c/c99/n1256.html) 註: pdf 版本來自[你所不知道的C語言:開發工具和規格標準](https://hackmd.io/@sysprog/c-standards) ,打開後右上角顯示 `ISO/IEC 9899:TC3`, 所以網頁版也是相同版本,由 google 搜尋關鍵字 `n1256 html draft` 。 ## 針對佇列操作的分支命名 `分支`命名如下: 以 `wip/` 開頭後接 `6` 個分支名稱: `alloc`,`insert`,`size` ,`delete` ,`sort` ,`queue`。 `wip/alloc`: 此`分支`存放 `q_new()`, `q_free()` 兩個函式的 `commit`。 `wip/insert`: 此`分支`存放 `q_insert_head()`, `q_insert_tail()`,`q_remove_head()`, `q_remove_tail()` 四個函式的 `commit`。 `wip/size`: 此`分支`存放 `q_size()` 一個函式的 `commit`。 `wip/delete`: 此`分支`存放 `q_delete_mid()`, `q_delete_dup()` 兩個函式的 `commit`。 `wip/sort`: 此`分支`存放 `q_sort()`, `q_ascend()`,`q_descend()`, `q_swap()`, `q_reverse()`, `q_reverseK()` ,`q_merge()` 七個函式的 `commit`。 `wip/queue`: 此`分支`合併上述所有的 `commit`。等到 `16` 個函式都完成且經測試沒問題,再合併到 `master`。 另外下面程式碼實作對應的 commit 來自 wip/queue。 由於前述分支是透過其他分支 rebase 再 merge 而得,因此會在對應的原始分支看到相同的 commit 但可能是 相同/不同的 hash。個人理解是除了 wip/queue 以外的分支都是個人整理用,只對內給自己看,好處是可以馬上知道這個分支做了哪些修改。一旦確認實作正確,隨時可合併出新的 wip/queue 分支 ## 針對佇列操作的程式碼實作 概述: 此作業延續自 [2024q1 Homework1 (lab0)](https://hackmd.io/@ihost1002/2024q1-Homework1),由於自身基礎不足及私人原因,無法完成,這次再次嘗試撰寫,目標先訂為完成所有 `make test` 測試,即執行結果為 `100/100`。目前結果為 `95/95`, `trace-017` 不符要求: `q_insert_head` 、 `q_insert_tail` 、 `q_remove_head` 、 `q_remove_tail` 要求執行後為 `constant time` ,細節及個人想法在對應實作內容 `q_insert_head` 裡。另外所有的程式碼可能很醜,請老師及同學見諒。 ### `q_size` 計算目前佇列的節點總量。去年的紀錄: commit [2c3dcd6](https://github.com/ihost1002/lab0-c_2024/commit/2c3dcd602e4aac9645006263ab38c3295af2a282),這次用 list_for_each 重寫 ```diff + /* Return zero if queue is NULL or empty */ - const struct list_head *count = head; - while (count->next != head) { - count = count->next; + const struct list_head *node = NULL; + list_for_each(node, head) ``` comit [fd560e4](https://github.com/ihost1002/lab0-c/commit/fd560e41c3781232a5b88c3e67e42bb7100a2b97) ### `q_new` 建立新的「空」佇列。去年的紀錄: commit [7eb7ee4](https://github.com/ihost1002/lab0-c_2024/commit/7eb7ee4ec942f5876b7fe56fdfed39347399eda9)、commit [97b841f](https://github.com/ihost1002/lab0-c_2024/commit/97b841f74ae52d0676f34f586b643c80d6f694f0)、commit [498c33b](https://github.com/ihost1002/lab0-c_2024/commit/498c33b745f684c93b73312a7cbb15ee6488fc87)。這次重寫,用到的內建函式用註解的方式說明,並說明 malloc 被 test_malloc 取代, commit message 只寫出應完成的結果, [commit 86d2439](https://github.com/ihost1002/lab0-c/commit/86d24392b5dafc35bec91abc26b5e724e891eb4f) ### `q_insert_head` 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則)。舊的紀錄: commit [360627d](https://github.com/ihost1002/lab0-c_2024/commit/360627dfe69f8d4461963a9ae63f75b6636a0772), 這次用輔助函式 q_insert 把實作內容寫在前者,並使用 function pointer: insert 依據 position 來判斷呼叫 list_add / list_add_tail , ```diff /* * Define pointer to function of type * void (*)(struct list_head *, struct list_head *) * refer to list_add(), list_add_tail(). */ +typedef void (*list_insert)(struct list_head *node, struct list_head *head); + /* Do q_insert* operation */ +static inline void insert_operation(list_insert operation, + struct list_head *node, + struct list_head *head) +{ + operation(node, head); +} /* Helper function of q_insert_* */ +static inline bool q_insert(struct list_head *head, char *s, bool position) +{ + ... + list_insert insert = position ? list_add : list_add_tail; + insert_operation(insert, &element->list, head); + ... +} bool q_insert_head(struct list_head *head, char *s) { - return true; + return q_insert(head, s, 1); } bool q_insert_tail(struct list_head *head, char *s) { - return true; + return q_insert(head, s, 1); } ``` commit [29b3593](https://github.com/ihost1002/lab0-c/commit/29b3593a1a6bebe3ee415394d454f236c5f078bc) 。前述實作無法通過 constant time 測試,當時第一個想到的是那篇論文 〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉,英文不好,所以我先參考了 **Holy** 同學整理的 [論文閱讀:Dude, is my code constant time?](https://hackmd.io/@Holy/BkwZl273w) 知道為 timing attack。 駭客會透過比較字串的時間長短而推論出正確的字串,我當時理解成這個想法:假設一個平行宇宙中,大家用某種餐點的時間都固定,然後一次只能吃一種餐點且用餐期間不會做其他事。大雄喜歡宜靜,想知道她喜歡吃什麼,接著宜靜走進某間餐館吃飯:假如咖喱飯 20 分鐘、 涼麵 15 分、其他餐點...等,那大雄在外面紀錄宜靜用餐的時間不就知道她喜歡吃什麼了嗎?那假如宜靜不想讓別人知道,她只要用餐完後做些其他的事情佔用時間,這樣大雄不就無法得知靜香到底吃了什麼! 回到實作內容,想法是讓**每次複製字串的時間都相同**,要做到這件事需要知道最大輸入的字串長度,經過測試 (2025/03/19) , qtest 上最大輸入的字串長度為 4092, 由於 4096 為 2 的倍數,所以我以前述數字為基準。實作時先用以下方法: s 字串複製到 element_t->value, 另外複製 4096 減去前述字串長度,完成複製總字元長度為至少 4096 個字元 ```diff + /* Fake copy */ + char fake_string[4096] = "1"; + fake_string[4096 - length - 1] = '\0'; + char fake_copy[4096] = {0}; + strncpy(fake_copy, fake_string, 4096 - length); ``` commit [10b23d7](https://github.com/ihost1002/lab0-c/commit/10b23d7d2416763f35ebb7dfc5056a0e73c51765),但仍無法通過 constant time 測試。後來想到了,可能是複製到 element_t->value 才行,改成 ```diff - char fake_string[4096] = "1"; - fake_string[4096 - length - 1] = '\0'; - char fake_copy[4096] = {0}; - strncpy(fake_copy, fake_string, 4096 - length); + const char *fake_string = "1"; + int goal = (4096 - length) >> 1; + for (int i = 1; i < goal; i++) + strncpy(element->value, fake_string, 2); ``` commit [b5c1e0b](https://github.com/ihost1002/lab0-c/commit/b5c1e0bf4bf40be3fc6fd92b86e5c71822b9d835), 但是 option simulation 1 測試時出現以下錯誤 `ERROR message: Corruption detected in block with address 0x???????????? when attempting to free it.`。 這是由於修改到 MAGICFOOTER 導致的錯誤,只把額外宣告的字元複製到 element_t->value 造成前者錯誤發生,這讓我產生疑惑,難道只能複製 s 的內容到前述 value 中?因此我重複複製 s 內容到 elemet_t->value,並確保至少複製總共 4096 個字元 ```diff - /* Fake copy */ - const char *fake_string = "1"; - int goal = (4096 - length) >> 1; - for (int i = 1; i < goal; i++) - strncpy(element->value, fake_string, 2); + /* + * Copy Strings to element_t->value and make sure to copy at least + * 4096 characters in length. + */ + for (int i = 0; i <= 4096; i += length) + strncpy(element->value, s, length); ``` commit [f0bb601](https://github.com/ihost1002/lab0-c/commit/f0bb601d500c20f0a6cb79a6abac77e7a4150827),誤打誤撞通過 constant time 測試,後來以為成功就做成紀錄,結果 trace 17 之前的**效能測試失敗**了。我應確實了解 〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉及 commit [b5c56e0](https://github.com/torvalds/linux/commit/b5c56e0cdd62979dd538e5363b06be5bdf735a09) 但沒做到。 ### `q_insert_tail` 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則)。舊的紀錄: commit [ee66c58](https://github.com/ihost1002/lab0-c_2024/commit/ee66c58bfc5f955f7a2dd6b481332b05de4ec4a2),其餘參考 q_insert_head ### `q_free` 釋放佇列所佔用的記憶體。舊的紀錄: commit [f24e239](https://github.com/ihost1002/lab0-c_2024/commit/f24e239b352420b3986eea193b54d56c0d318e4e) 是錯的,修正後可執行 ```diff struct list_head *q = head->next; - element_t *t = NULL; while (q->next != head) { struct list_head *r = q->next; - free(q); + INIT_LIST_HEAD(q); + t = container_of(q, element_t, list); + free(t->value); + free(t); q = r; } - free(q); + INIT_LIST_HEAD(q); + t = container_of(q, element_t, list); + free(t->value); + free(t); free(head); ``` commit [4202d34](https://github.com/ihost1002/lab0-c_2024/commit/4202d34e8e6e03290c08baad0009907634493d22) 。這次用內建 list_for_each_safe 走訪所有節點 、 INIT_LIST_HEAD 斷開鏈結 、 q_release_element 釋放對應記憶體, commit [d80404d](https://github.com/ihost1002/lab0-c/commit/d80d04d95e2fa93c4821f20b1083b2fc9a2c4712) ### `q_remove_head` 在佇列開頭 (head) 移去 (remove) 給定的節點。舊的紀錄: commit [48ba2b7](https://github.com/ihost1002/lab0-c_2024/commit/48ba2b7d44154cb0a3125594ce618be61092ad4f), 含 q_remove_tail 實作, 除了移除節點的是 頭 或 尾 其餘是重複的程式碼。後來跑 traces/trace-xx 的時候發現錯誤,修正後 ```diff - removed_e->value[bufsize - 1] = '\0'; + size_t str_n = strlen(removed_e->value); + size_t size = str_n <= bufsize ? str_n + 1 : bufsize; + if (str_n > bufsize) { + removed_e->value[size - 1] = '\0'; + } - strncpy(sp, removed_e->value, bufsize); + strncpy(sp, removed_e->value, size); - removed_e->value[bufsize - 1] = '\0'; + size_t str_n = strlen(removed_e->value); + size_t size = str_n <= bufsize ? str_n + 1 : bufsize; + if (str_n > bufsize) { + removed_e->value[size - 1] = '\0'; + } - strncpy(sp, removed_e->value, bufsize); + strncpy(sp, removed_e->value, size); ``` commit [97fee47](https://github.com/ihost1002/lab0-c_2024/commit/97fee47713c25271558a5ac3837f485265fba3dd)。 這次重寫,使用輔助函式 q_remove 避免重複相同的程式碼及 position 判斷是移出 頭 或 尾端節點 ```diff /* Helper function of q_remove_* */ +static inline element_t *q_remove(struct list_head *head, + char *sp, + size_t bufsize, + bool position) +{ + /* Set terminating char */ + size_t string_length = strlen(element->value); + size_t size = string_length <= bufsize ? (string_length + 1) : bufsize; + if (string_length > bufsize) + element->value[size - 1] = '\0'; + /* Copy string:value of element into sp */ + strncpy(sp, element->value, size); +} element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { - return NULL; + return q_remove(head, sp, bufsize, 1); } element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { - return NULL; + return q_remove(head, sp, bufsize, 0); } ``` commit [2034b7f](https://github.com/ihost1002/lab0-c/commit/2034b7fa5f1184efabe2552606e1bcfcb2772f4a) ### `q_remove_tail` 在佇列尾端 (tail) 移去 (remove) 給定的節點。參考 q_remove_head ### `q_delete_mid` 移走佇列中間的節點。舊的紀錄: commit [b2190a3](https://github.com/ihost1002/lab0-c_2024/commit/b2190a36ad3a12a9625c8a80cb6dbd13d4534b22)。 這次參考[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-prog/%2Fs%2FSkE33UTHf) 其中快慢指標技巧重寫, commit [cdcf471](https://github.com/ihost1002/lab0-c/commit/cdcf471407cb4da0982485f0bcb7298f1d535e47)。 ### `q_delete_dup` 在已經排序的狀況,移走佇列中具備重複內容的節點。舊紀錄: commit [eb8ac29](https://github.com/ihost1002/lab0-c_2024/commit/eb8ac29144c3e0ba7134024f32b8d96167db7cdf)。 本次些微修改變數名稱及使用內建函式 q_release_element 重寫, commit [ec3fa27](https://github.com/ihost1002/lab0-c/commit/ec3fa272a7c2b47f3e7dfe89b979aba4c2d39b70)。使用 indirect pointer 好處在移除節點時不用更新節點內容 ### `q_swap` 交換佇列中鄰近的節點。舊紀錄: commit [e592eb6](https://github.com/ihost1002/lab0-c_2024/commit/e592eb674ded5ba90d2b096131180d40f19455f6), 當初是想用 xor 來交換數值,但發現無法使用 unitptr_t, 所以就自建輔助程式 swap 來交換每個節點對應的 next 、 prev 的值,現在來看寫得很醜,可能會看不懂。這次重寫發現其功能等同呼叫 q_reverseK 其 k 為 2 ,後續參考 q_reverseK ### `q_reverse` 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點。與 q_reverseK 合併,功能等於前述函式帶入 k = 總節點數,參考 q_reverseK ### `q_reverseK` 類似 q_reverse,但指定每 k 個節點為一組,進行反向順序排列。舊紀錄: commit [29763b4](https://github.com/ihost1002/lab0-c_2024/commit/29763b453b3cb8707276bf61775806807e45f7fe)。也有使用 commit [e592eb6](https://github.com/ihost1002/lab0-c_2024/commit/e592eb674ded5ba90d2b096131180d40f19455f6) 的 swap, 所以很醜。本次與 q_swap 、 q_reverse 合併,用 list_move 、 list_move_tail 完成節點交換, commit [a1d7692](https://github.com/ihost1002/lab0-c/commit/a1d7692805dec79a439883b25f653b8c73798753)。 使用 indirect pointer 好處在搬移節點時不用更新節點內容 ### `q_sort` 以遞增/遞減順序來排序鏈結串列的節點。 本來想自己想一個符合時間複雜度 O(nlogn) 的實作,但結果是怎樣想都只想出 O(n^2^) , commit [f9001a0](https://github.com/ihost1002/lab0-c_2024/commit/f9001a044d62d9470c11d694ada3536777e96ccf),不得已只好參考 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-prog/%2Fs%2FSkE33UTHf) 其中 遞迴方式 且 top-down 的 merge sort 實作,但沒有真懂。用到自建的輔助函式 merge_two_list 來合併兩個佇列, merge_sort_list 遞迴呼叫自己並在把佇列從中切兩半,後半用 mid 作佇列頭,再遞迴呼叫自己完成 merge sort 實作, commit [af4ec1d](https://github.com/ihost1002/lab0-c/commit/af4ec1d455955e4ee45c0afcbc8f686d5b85b5a2) ### `q_ascend` `element_t ` 結構有兩個成員: `value` 、 `list` , 每個節點都是前述**結構**中的 `list` ,而 `value` 儲存`字串` 。對每個節點 n1 ,其右邊節點 n2 對應的`字串`與自身`字串`相比為`小`時刪除此節點 n1。舊紀錄: commit [c58a2f4](https://github.com/ihost1002/lab0-c_2024/commit/c58a2f498151606ed64639832c2a6020466fde56) 含 q_descend 實作。本次自建輔助函式 q_delete_strictly 取代重複的程式碼,作法是從最後的節點往前找,帶入 descend 決定是 q_ascend 還是 q_descend, commit [ff9b18d](https://github.com/ihost1002/lab0-c/commit/ff9b18dd2b3c994366c841397363480addbe64e2) ### `q_descend` 與 q_ascend 相似,比較結果為`大`時刪除節點 n1。參考 q_ascend ### `q_merge` 合併所有已排序的佇列,並合而為一個新的已排序佇列。目前是一個可執行的實作,但是當合併越多時越沒效率,commit [c68ad41](https://github.com/ihost1002/lab0-c/commit/c68ad4187b460aa349845d4eaf2e50eabdf6a0e3) ## 閱讀指定的論文〈[Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)〉 ### 以及 commit [b5c56e0](https://github.com/torvalds/linux/commit/b5c56e0cdd62979dd538e5363b06be5bdf735a09) 進行中