# 2024q1 Homework1 (lab0) contributed by < [YiChiChao](https://github.com/YiChiChao) > ### Reviewed by `yu-hsiennn` 多利用 `graphviz` 來製圖 ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz CPU family: 6 Model: 142 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 12 CPU max MHz: 3900.0000 CPU min MHz: 400.0000 BogoMIPS: 3600.00 ``` ## 名詞中英定義 >過去整理筆記很習慣中英夾雜,透過這學期的學習希望能改變這樣的行為。 * linked list : 鏈結串列 * traverse : 走訪 * memory hierarchy : 記憶體階層架構 * generic programming : 泛用性 (?) ## 資料閱讀筆記 ### 你所不知道的 C 語言: linked list 和非連續記憶體 * (待解答)如何偵測鏈結串列是否存在環狀結構? * (待解答)如何對鏈結串列排序並確保**空間複雜度**為 $O(1)$呢? * (待解答)什麼是 Locality of reference * 有品味的寫法(以`remove_list_node`為例): * 直覺:用指標指向第一個元素資料,此種方法需要考慮到此筆資料是否為第一筆資料,如果是,則`list->head` 和 `prev->next`都需要更新。 * 有品味:用「指標的指標」指向 `&list->head`,以「要更新的位址」為思考點來操作,而後指標的指標皆為指向 `&node->next`。刪除的操作,實質上是去更新特定指標的位址中的內容,而不是單就個別的node 去判別。 * 針對移走節點的間接指標版本 ```c typedef struct list_entry { int value; struct list_entry *next; } list_entry_t; // Given that there is a remove function in stdio.h, I have renamed it to // remove_element to avoid potential conflicts. The for loop needs to account // for scenarios both with and without the target value. The indirect variable // cannot be included in the for loop because its new value needs to be assigned // at the end. void remove_element(list_entry_t **head, int value) { list_entry_t **indirect = head; for (; (*indirect)->value != value || (*indirect)->next == NULL; indirect = &((*indirect)->next)) { } *indirect = (*indirect)->next; } ``` :::danger 使用作業規範的程式碼縮排風格。 ::: ## 指定的佇列操作 ### `q_new` 透過 `malloc` 配置記憶體,並且透過 `list.h` 中的 `INIT_LIST_HEAD` 初始結構體。 :::danger 無論標題和內文中,中文和英文字元之間要有空白字元 (對排版和文字搜尋有利) ::: `list.h` 中有 `LIST_HEAD(head)` 和 `INIT_LIST_HEAD(struct list_head *head)` 兩個功能很類似的函式/巨集。`LIST_HEAD` 是直接宣告一個區域變數並且完成初始化的巨集,`INIT_LIST_HEAD` 只負責初始化傳入的 `list_head` 指標。 :::danger allocate 翻譯為「配置」,以區隔 dispatch (分發/分配) 的翻譯,二者在作業系統領域都是常見詞彙。 > 了解,已修正。[name=YiChi Chao] ::: 之所以不能直接用 `LIST_HEAD(head)` 而要動態配置記憶體,再用 `INIT_LIST_HEAD` 初始化指標,是因為在函式中宣告的區域變數會被配置到 stack 中,當函式返回時會自動將此區域回收,此時即使回傳此結構體的指標,指標的內容也可能為未定義之內容。透過 `malloc` 配置的記憶體在 heap 中,此區的記憶體需要透過 `free` 來釋放。所以 `malloc` 配置的記憶體的結構體指標回傳後,其內容並不會因為函式結束而被回收。 ```c struct list_head *q_new() { // instead of using LIST_HEAD creating local variable // use INIT_LIST_HEAD and malloc for both declaration and initialization struct list_head *new_queue = malloc(sizeof(struct list_head)); INIT_LIST_HEAD(new_queue); return new_queue; } ``` ### `q_free` ```c void q_free(struct list_head *l) { free(l); } ``` ### `q_insert_head` :::danger string 是「字串」。 ::: `strdup` 是用來動態複製一個以 `'\0'` 結尾的字串,並回傳字串指標。 >strdup(3) — Linux manual page,"The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3)." Delete 的時候在處理記憶體問題時,除了結構體本身,要記得處理字串本身記憶體的回收。 ```c /* Insert an element at head of queue */ bool q_insert_head(struct list_head *head, char *s) { if (!head) { // false for queue is NULL return false; } element_t *nownode = malloc(sizeof(element_t)); assert(nownode); if (!nownode) { return false; } nownode->value = strdup(s); if (!nownode->value) { free(nownode); return false; } list_add(&(nownode->list), head); return true; } ``` 在 commit 時遇到 error ```c git commit Following files need to be cleaned up: queue.c queue.c:69:5: error: Memory leak: nownode [memleak] return true; ^ Fail to pass static analysis. ``` 最後的解決方式是將 `&(nownode->next)` 改成 `&nownode->next` 。 ### `q_insert_tail` 從 [terry23304](https://hackmd.io/@normal/SyAQbn26j#q_insert_headtail) 看到可重用 `q_insert_head` 的建議,將插入 `Head` 的尾看作插入 `Head->prev` 的頭。 ![1709203137788](https://hackmd.io/_uploads/rkB0ARa26.jpg) ```c bool q_insert_tail(struct list_head *head, char *s) { return q_insert_head(head->prev, s); } ``` :::danger HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」 > 了解,先刪除此部份 ::: ### `container_of` ```c #define container_of(ptr, type, member) \ ({ \ void *__mptr = (void *) (ptr); \ static_assert(__same_type(*(ptr), ((type *) 0)->member) || \ __same_type(*(ptr), void), \ "pointer type mismatch in container_of()"); \ ((type *) (__mptr - offsetof(type, member))); \ }) ``` 由已知結構體中的 **某個成員起始位址** 去獲得此成員所在之 **結構體的起始位址**。 利用成員起始位址`ptr`扣掉成員在結構體中相對位址。 ![received_265319079844962](https://hackmd.io/_uploads/HJ3BRKn2a.jpg) :::danger 避免非必要的項目列表 (即 `* `),以清晰且明確的漢語書寫。 ::: ## `q_delete_dup` 此函式的目的是要消除佇列中重複的字串節點。在條件判斷會有三種情況: 1. 目前的節點字串和前一個節點字串相同 2. 目前的節點字串和前一個節點字串不同,但和下一個節點字串相同 3. 目前的節點字串和前一個節點字串不同,且和下一個節點字串不同 前兩者皆為字串重複,第二種除了消除目前節點外,還需要更新紀錄重複字串之變數( `prob` ) 原本更新 `prob` 的寫法為直接透過 `strdup` 重新動態配置字串空間,紀錄最新重複字串。 ```c if (prob == NULL || strcmp(prob, cur->value)) { // Check if the current node has the same string // as the next node if (next == NULL || strcmp(cur->value, next->value)) { continue; } else { // Update the prob if cur and next have the same string prob = strdup(cur->value); } } //printf("Free: %s\n", cur->value); list_del(&cur->list); free(cur->value); free(cur); ``` 當透過 `make check` 搭配 `trace-06-ops.cmd` 測試時,會發現有兩個配置的位置沒有被釋放 ```c make check CC queue.o LD qtest ./qtest -v 3 -f traces/trace-06-ops.cmd # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK l = [] l = [tvzaist vfktis prljcykrc jxiswnclm] l = [tvzaist vfktis prljcykrc jxiswnclm gerbil gerbil gerbil] l = [tvzaist vfktis prljcykrc jxiswnclm gerbil gerbil gerbil lion lion] l = [tvzaist vfktis prljcykrc jxiswnclm gerbil gerbil gerbil lion lion zebra zebra] l = [gerbil gerbil gerbil jxiswnclm lion lion prljcykrc tvzaist vfktis zebra zebra] l = [jxiswnclm prljcykrc tvzaist vfktis] l = NULL ERROR: There is no queue, but 2 blocks are still allocated ``` 更仔細地印出目前釋放位置的字串內容才發覺,當透過 `strdup` 來更新 `prob` 的字串時,未釋放前一個 `prob` 字串的位置。 ```c l = [gerbil gerbil gerbil jxiswnclm lion lion prljcykrc tvzaist vfktis zebra zebra] Free: gerbil Free: gerbil Free: gerbil Free: lion Free: lion Free: zebra Free: zebra Free Prob: zebra ... ERROR: There is no queue, but 2 blocks are still allocated ``` 在更新 `prob` 前,先釋放目前字串之位置,將 `prob` 設為 NULL ,再進行更新。 ```c if (prob) { free(prob); prob = NULL; } prob = strdup(cur->value); ``` :::warning 改為以下: ```c free(prob); prob = strdup(cur->val); ``` 注意看 [free(3)](https://man7.org/linux/man-pages/man1/free.1.html)。 :::