# 2024q1 Homework1 (lab0)
contributed by < [`poabob`](https://github.com/POABOB/lab0-c) >
## 開發環境
> Mackbook Pro M1 with lima.
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ lscpu
Architecture: aarch64
CPU op-mode(s): 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Vendor ID: 0x00
Model: 0
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
Stepping: 0x0
BogoMIPS: 48.00
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp a
simdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 asimddp
sha512 asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpo
dp flagm2 frint
NUMA:
NUMA node(s): 1
NUMA node0 CPU(s): 0-3
```
## 實作指定的佇列操作
### `q_new`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
:::danger
allocate 的翻譯是「配置」,以區格 dispatch (分發/分配) 的翻譯
:::
先<s>分配</s> 記 `list_head` 憶體空間,如果分配不到記憶體直接返回 `NULL`;反之,就可以使用 [`void INIT_LIST_HEAD(struct list_head *list)`](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.INIT_LIST_HEAD) 來初始化一個空的鏈結串列。
```c
struct list_head *q = malloc(sizeof(struct list_head));
/* If q didn't allocate space */
if (!q) return NULL;
/* Initialize a list_head structure */
INIT_LIST_HEAD(q);
```
在 [list.h](https://github.com/torvalds/linux/blob/805d849d7c3cc1f38efefd48b2480d62b7b5dcb7/include/linux/list.h#L35) 中,可以發現 `INIT_LIST_HEAD()` <s>函數</s> 函式是使用 `WRITE_ONCE()` 對來將 `next` 和 `prev` 指定為自己。
:::danger
對應到數學公式,function 應翻譯為「函數」,後者就像是台「機器」,它能夠將集合 A 裡面的每個元素「唯一地」對應到集合 B 裡的一個元素。但在 C 語言一類的通用程式語言中,function 未必具備數學函數的意涵,也就是缺乏「唯一對應」的特性,例如對於 [time](https://man7.org/linux/man-pages/man2/time.2.html) 來說,給定同樣的輸入,卻會在不同時間點得到不同的結果,在 C 語言一類的通用程式語言中,甚至可省略傳回值,後者就不符合「函數」的行為。因此,當在通用的程式設計中,function 應翻譯為「函式」,表示「衍生自數學函數的常式 (routine)」。另外,對於更一般的描述,function 會指「功能」一類的意涵,應合適挑選譯詞。
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
```c
static inline void INIT_LIST_HEAD(struct list_head *list)
{
WRITE_ONCE(list->next, list);
WRITE_ONCE(list->prev, list);
}
```
在 [linux/tools/include/linux/compiler.h](https://github.com/torvalds/linux/blob/805d849d7c3cc1f38efefd48b2480d62b7b5dcb7/tools/include/linux/compiler.h#L200C1-L206C3) 中,是使用 `marco`,而不是單純的 `list->next = list`,在 [你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Linux-%E6%A0%B8%E5%BF%83%E5%8E%9F%E5%A7%8B%E7%A8%8B%E5%BC%8F%E7%A2%BC) <s>有非常詳細的說明</s>。
:::danger
不用特別提「有非常詳細的說明」,本該如此,你該闡述自己的洞見。
:::
```c
#define WRITE_ONCE(x, val) \
({ \
union { typeof(x) __val; char __c[1]; } __u = \
{ .__val = (val) }; \
__write_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
```
簡單來說,看到 `volatile` 關鍵字,主要是告訴編譯器:
1. 這筆操作不能進行 `編譯器優化`,避免 `執行順序` 可能發生變換。
1. 確保 CPU `不會從暫存器讀值`,而是 `從記憶體重新讀`。
:::danger
查閱 C 語言規格書,並予以解讀。
:::
### `q_size`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
使用 `list_for_each` 來計算整個鏈結串列有幾個節點。
```c
int q_size(struct list_head *head)
{
if (!head) return 0;
int len = 0;
struct list_head *li;
list_for_each (li, head) len++;
return len;
}
```
> `list_for_each` 只能使用 `不刪除節點` 的時候使用,若是 `需要刪除節點` 就需要使用 [`list_for_eac_safe`](https://hackmd.io/@sysprog/c-linked-list#list_for_each_safe--list_for_each_entry_safe),這個巨集會幫你記錄 next 節點,避免當前節點被刪除找不到下一個節點在哪。
### `q_free`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
在 [Linux 核心原始程式碼巨集: container_of](https://hackmd.io/@sysprog/linux-macro-containerof#container_of-%E5%B7%A8%E9%9B%86%E4%BD%9C%E7%82%BA%E8%B3%87%E6%96%99%E5%B0%81%E8%A3%9D%E7%9A%84%E5%9F%BA%E7%A4%8E) 中有提到,`container_of` 則在 `offsetof` 的基礎上,擴充為「給定成員的位址、struct 的型態,及成員的名稱,**傳回該結構體的位址**」。
使用 while 來逐一找尋元素的地址,再透過 `q_release_element` 將該節點釋放。
```c
void q_free(struct list_head *head)
{
if (!head) return;
struct list_head *current = head->next;
while (head != current) {
element_t *e = container_of(current, element_t, list);
current = current->next;
q_release_element(e);
}
free(container_of(head, element_t, list));
}
```
:::danger
\# Delete queue. Goes back to a NULL queue.
ERROR: Attempted to free unallocated block. Address = 0xaaaae2c479d8
ERROR: Attempted to free unallocated or corrupted block. Address = 0xaaaae2c479d8
ERROR: Corruption detected in block with address 0xaaaae2c479d8 when attempting to free it
Segmentation fault occurred. You dereferenced a NULL or invalid pointermake: *** [Makefile:57: check] Aborted (core dumped)
:::
這邊遇到 `Segmentation fault occurred` 原因是因為 `head` 本身就沒有 `element_t`,我卻使用 `container_of` 去找元素的位址,理所當然回傳一個 NULL。
> commit: [d803afa](https://github.com/POABOB/lab0-c/commit/d803afa2670e3baaca42e0c4f4b39621aa6c7de0)
```diff
- free(container_of(head, element_t, list));
+ free(head);
```
### `q_insert_head`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
在新增一個元素時,先申請 `element_t` 記憶體空間,然後原本想使用 `strcpy`,但是看到別人使用 `strdup` 後,才知道 `strdup` 會順便申請該字串的記憶體空間,並返回該指標。
:::danger
assign 不該偷懶地翻譯為「賦值」,明明 assign 是個常見詞彙,你何時在英語課程聽到「賦值」的解釋?本例來說,可寫作「將指標的內含值指派給某變數」。
改進你的漢語表達。
:::
再來將 `c` 的指標<s>賦值</s> 給 `el->value` ,使用 [Linux Kernel API](https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.list_add) 的 `list_add` 將其與 `head` 串起來。
```c
element_t *el = malloc(sizeof(element_t));
if (!el) return false;
char *c = strdup(s);
if (!c) {
free(el);
return false;
}
el->value = c;
list_add(&el->list, head);
```
### `q_insert_tail`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
這個函數與 [`q_insert_head`](https://hackmd.io/cHKtZ0waRkyPaVDzWZHb2w?both#q_insert_head) 邏輯一樣,只不過最後使用 `list_add_tail()`。
### `q_remove_head`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
先判斷 head 為 `NULL` 還是 `empty`,避免後續操作失敗。
再使用 `list_first_entry()` 獲取第一個元素,也就是要刪除並返回的。
透過 `strncpy` 將該元素的值複製到 `sp`,並在最後用 `\0` 來結尾。
> 這邊使用 `strncpy` 而不是 `strcpy` 是避免 `sp` 的空間如果不夠,就會產生 `buffer overflow`,`strncpy` 可以控制複製長度。
最後,把該元素從鏈結串列中刪除,返回該元素。
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head)) return NULL;
element_t *target = list_entry(head, element_t, list);
strncpy(sp, target->value, bufsize - 1);
sp[bufsize - 1] = '\0';
list_del(head->next);
return target;
}
```
### `q_remove_tail`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
這個<s>函數</s> 與 [`q_remove_head`](https://hackmd.io/cHKtZ0waRkyPaVDzWZHb2w?both#q_remove_head) 邏輯一樣,只不過最後使用 `list_last_entry()`。
### `q_delete_mid`
> commit: [cc603ed](https://github.com/POABOB/lab0-c/commit/cc603ed7f2afa0f5fa3f962c47a597626d04fd89)
這個函數可以定義兩個指標,分別叫做 `slow` 和 `fast`。
- `slow` 每次迭代的時候會走訪一個節點。
- `fast` 每次迭代的時候會走訪兩個節點。
這樣當 `fast` 走訪結束之後,`slow` 就會是中間節點,就可以把它刪除。
```c
bool q_delete_mid(struct list_head *head)
{
// https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/
if (!head || empty(head)) return false;
struct list_head *fast = head->next, *slow = head->next;
while (fast != head && fast->next != head) {
fast = fast->next->next;
slow = slow->next;
}
list_del(slow);
q_release_element(list_entry(slow, element_t, list));
return true;
}
```
### q_delete_dup
> commit: [bfa16e4](https://github.com/POABOB/lab0-c/commit/bfa16e46a2306ddc6e163af6d9667a88cd41de25)
假定一個鏈結串列已經經過排序了,那麼可以使用 `list_for_each_safe` 來判斷 `左 (i)` `右 (i+1)` 兩個節點中的元素是否相同。
如果相同就把 `左節點 (i)` 給刪除,並往下走訪接續的節點。
最後要注意如果當 `右節點 (i+1)` 與我們的 `head` 相同,就代表該鏈結串列已走訪完畢。
```c
struct list_head *left, *right;
list_for_each_safe (left, right, head) {
element_t *l_el = list_entry(left, element_t, list),
*r_el = list_entry(right, element_t, list);
if (left->next != head && strcmp(l_el->value, r_el->value) == 0) {
list_del(left);
q_release_element(l_el);
}
}
```
### q_reverse
> commit: [3cdf0f8](https://github.com/POABOB/lab0-c/commit/3cdf0f84ccba62ec7bea03e29c4360e1c3194579)
這個函數也是可以用 `list_for_each_safe` 來執行,把 `left->next` 指向 `left->prev`,再把 `left->prev` 指向 `right`,這樣就可以逐一將每個節點進行反轉。
```c
struct list_head *left, *right;
list_for_each_safe (left, right, head) {
left->next = left->prev;
left->prev = right;
}
```
### q_swap
> commit: [085613b](https://github.com/POABOB/lab0-c/commit/085613b890cbbe1f246d0137019e9856453caad3)
```c
struct list_head *left, *right;
list_for_each_safe(left, right, head) {
if (!count) {
struct list_head *temp = left;
left->next = right->next;
left->prev->next = right;
left->prev = right;
right->next = left;
right->prev = temp->prev;
count = 1;
} else if (count) {
count = 0;
right->prev = left->next;
}
}
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::