--- title: 2025q1 Homework1 (lab0) --- # 2025q1 Homework1 (lab0) contributed by < [`duckcone`](https://github.com/duckcone) > ### Reviewed by `HeatCrab` > 只要 `next != head` 就判斷 `current` 節點的字串是否與 `next` 節點的字串相同,若是相同就將 `next = next->next`。 這裡的 while 迴圈,可以使用 `list.h` 中的 `list_for_each_safe` 來取代,增加可讀性。 簡單實作如下: ```diff + struct list_head *safe; - while (next != head) { + list_for_each_safe(next, safe, head) { const element_t *next_element = list_entry(next, element_t, list); if (strcmp(current_element->value, next_element->value) == 0) { - next = next->next; + break; - } else { - break; } + if (next == head) break; } ``` > 設定 `dup_head->prev->next = next; next->prev = dup_head->prev` 來將重複的節點斷開連接。 可以使用 `list.h` 中的 `list_cut_position` 來簡化。 {%hackmd NrmQUGbRQWemgwPfhzXj6g %} ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 Copyright (C) 2023 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. $ uname -a Linux oslab 6.8.0-54-generic #56-Ubuntu SMP PREEMPT_DYNAMIC Sat Feb 8 00:37:57 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 48 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 16 On-line CPU(s) list: 0-15 Vendor ID: AuthenticAMD Model name: AMD Ryzen 7 7800X3D 8-Core Processor CPU family: 25 Model: 97 Thread(s) per core: 2 Core(s) per socket: 8 Socket(s): 1 Stepping: 2 ``` ## 指定的佇列操作 ### q_new() 建立一個空佇列。利用 `malloc()` 配置一塊記憶體,並且利用函式 `INIT_LIST_HEAD();` 對佇列進行初始化。 在測試中發現原先程式碼並未考慮 `malloc` 失敗的問題,因此加入了判斷 `malloc` 是否成功的程式碼。 ```diff struct list_head *q_new() { struct list_head *head = malloc(sizeof(struct list_head)); + if (!head) + return NULL; INIT_LIST_HEAD(head); return head; ``` ### q_free() 釋放佇列的空間。利用 `list_for_each_entry_safe()` 走訪每個包含 `list_head` 結構體的每個節點,並且利用 `qrelease_element()` 釋放節點。 ### q_insert_head() 新增一個節點在佇列的起始位置。透過呼叫 `create_new_element() ` 函式來新增新的 `element_t`,最後透過 `list_add()` 函式將節點新增在佇列的開頭位置。 **create_new_element() 的實作細節** 1. 在進行 `element_t *new_element = malloc(sizeof(element_t))` 之後須判斷是否成功,若 `malloc()` 失敗則釋放其記憶體並傳 `NULL`。 2. 透過 `strdup()` 將參數 `s` duplicate 一份,並且將節點的 `value` 指向其位址。 #### 如果 `*s` 是 NULL pointer 呢? 參考 [2024 年系統軟體系列課程討論區](https://www.facebook.com/groups/system.software2024/permalink/1556863905091502/?mibextid=oMANbw) 社團中的這篇貼文所提到的問題,發現自己的程式碼無法確認使用者在新增節點時所輸入的字串為何,因此在 `q_insert_head()` 以及 `q_insert_tail()` 中新增了判斷字串的程式碼,透過 `if(!s || !*s)` 來確保指標 `s` 所指向的位址非 `NULL`或字串非 `NULL` 。 ```diff= bool q_insert_head(struct list_head *head, char *s) { - if (!head) + if (!head || !s || !*s) return false; element_t *new_element = create_new_element(s); if (!new_element) return false; list_add(&new_element->list, head); return true; } ``` ### q_insert_tail() 與 `q_insert_head()` 的實作方式相同,透過呼叫 `element_new()` 函式來新增新的節點,並透過 `list_add_tail()` 將節點新增在佇列的尾端。 ### q_remove_head() 原先的想法是先取得佇列開頭的節點位址,並且調整 `prev` 以及 `next` 所指向的位址。但是在進行 `$ make test` 後,發現在進行 `Test of insert_head and remove_head` 測試時會有以下錯誤訊息: ```shell ERROR: Attempted to free unallocated block. Address = 0x558a5a52e278 ERROR: Attempted to free unallocated or corrupted block. Address = 0x558a5a52e278 Segmentation fault occurred. You dereferenced a NULL or invalid pointer ``` 在閱讀 `queue.h` 檔案時不夠仔細,忽略了以下針對 `q_remove_head()` 的說明: > If sp is non-NULL and an element is removed, copy the removed string to `*sp` (up to a maximum of bufsize-1 characters, plus a null terminator.) 因此針對此函式進行修正。 ```diff element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { - if (!head) + if (!head || list_empty(head)) + return NULL; + + element_t *head_element = list_first_entry(head, element_t, list); + if (!head_element) return NULL; - element_t *head_element = container_of(head, element_t, list); - head->next->prev = head->prev; - head->prev->next = head->next; + if (!sp) + return head_element; + memset(sp, '\0', bufsize); + strncpy(sp, head_element->value, bufsize); + list_del(&head_element->list); return head_element; } ``` ### q_remove_tail() 與 `q_remove_head()` 的實作方式相同,唯一的差別在於利用 `list_last_entry()` 尋找佇列的最後一個節點。 ### q_size() 利用 `list_for_each()` 逐一走訪每個節點來計算佇列中的節點數量。 ### q_delete_dup() 實作上分為兩個步驟 1. 尋找重複的節點 2. 刪除重複的節點 #### 尋找重複的節點 1. 利用 `list_for_each(current, next, head)` 來逐一走訪每個節點,並且同時擁有當前節點 `current` 以及下個節點 `next` 的位址。 2. 只要 `next != head` 就判斷 `current` 節點的字串是否與 `next` 節點的字串相同,若是相同就將 `next = next->next`。 3. 一旦 `next == head` 或者 `current` 節點的字串與 `next` 節點的字串不同,便會跳出尋找重複節點的迴圈。這時從 `current` 到 `next->prev` 節點的字串內容都是相同的。 #### 刪除重複的節點 1. 設定 `dup_head = current` 表示所有重複節點的起始節點。 2. 設定 `dup_head->prev->next = next; next->prev = dup_head->prev` 來將重複的節點斷開連接。 3. 利用 `temp` 節點作為 `dup_head` 的下一個節點。並在刪除 `dup_head` 所指向的節點之後將 `dup_head = temp` 。反覆執行此操作直到 `dup_head == next` 即可將所有重複的節點刪除。 4. `list_for_each_safe()` 會在每一次迭代將 `current = next` 。 ### q_swap() 參考自 [2025-02-25 問答簡記 HerryChaing](https://hackmd.io/l4--gpsZQBiEI5LKS1lDvg?view#HenryChaing) 的實作方式。 初始化 `current` 為 `head->next` 。當 `current` 以及 `current->next` 都不等於 `head` 節點時,利用 `list_move` 將 `current` 與 `current->next` 的節點互換,並且繼續走訪佇列。 查看 `list_move()` 的實作可以發現該函式呼叫 `list_add(node, head)` ,在 `list_add()` 中之所以是將 `node` 加入至 `head->next->prev` 是因為在佇列的 `head` 本身只有提供尋找佇列開頭的功能,並且並無嵌入在 `element_t` 的結構中,因此需要透過 `head->next` 才是佇列中的第一個節點。 透過 `list_move(node, head)` 的特性,正好可以將,`node` 與 `head` 的位置互換。 ### q_reverse() 利用 `list_for_each_safe()` 逐一走訪每一個節點,並且將 `current` 節點的 `next` 與 `prev` 所指向的位址互換。由於 `list_for_each_safe()` 的中止條件為 `node == head` ,因此迴圈結束後需要再將 `head` 節點的 `next` 以及 `prev` 所指向的位址互換。 ### q_reverseK() 當以下四種情況時不須進行節點反轉 1. `head` 為 `NULL` 時 2. `head` 所指向的佇列為空佇列時 3. `head` 所指向的佇列只有一個節點時 4. 反轉的節點數量 `k` 為 0 時 首先利用 `list_for_each_safe(current, safe, head)` 逐一走訪每個節點,並且利用計數器 `counter` 來計算走訪的節點數量,當 `counter >= k` 時便進行反轉: 1. 利用 `list_move_tail(current->prev, safe)` 將 `current` 的前一個節點移動至 `safe` 節點之前 2. 將 `counter` 值減 1 3. k 個元素只需要做 k - 1 次的交換即可完成反轉,因此完成反轉時 `counter` 的值為 1 4. 將 `counter` 歸 0 ### q_delete_mid() 透過兩個指標 `head_ptr` 以及 `tail_ptr` 對 `head` 的 `prev` 以及 `next` 方向逐一走訪,當兩個指標相等(節點數量為奇數),或是 `head_ptr->next` 等於 `tail_ptr` (節點數量為偶數) 時,即中間節點的位置。