# 2024q1 Homework1 (lab0)
contributed by < [`duckcone`](https://github.com/duckcone) >
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
$ 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) i7-6700 CPU @ 3.40GHz
CPU family: 6
Model: 94
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 3
CPU max MHz: 4000.0000
CPU min MHz: 800.0000
BogoMIPS: 6799.81
```
## 指定的佇列操作
### q_new()
建立一個空佇列。利用 `malloc()` 配置一塊記憶體,並且將指標 `prev` 以及 `next` 都指向此位址。
在後續的測試中,發現原先程式碼並未考慮 `malloc` 失敗的問題,因此加入了判斷 `malloc` 是否成功的程式碼。
> commit: [5088d8b](https://github.com/duckcone/lab0-c/commit/5088d8bc30171726d16b3e46f9ca858b30e25283)
```diff
struct list_head *q_new()
{
struct list_head *head = malloc(sizeof(struct list_head));
+ if (!head)
+ return NULL;
head->prev = head;
head->next = head;
return head;
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::
### q_free()
釋放佇列的空間。利用 `list_for_each_entry_safe` 拜訪鏈結串列的每個節點,並且使用 `q_release_element()` 釋放節點。其中 `q_release_element()` 的 parameter 為 `element_t` 型態。
### q_insert_head()
新增一個節點在佇列的起始位置。會呼叫 `element_new()` 函式來新增新的 `element_t`。最初的構想是 直接將 `element_t` 中的 `value` 直接指向所傳入的 parameter `s`
```c
element_t *element_new(char *s)
{
elemnt_t *new_element = malloc(sizeof(element_t));
new_element->value = s;
return new_element;
}
```
但經由 `make check` 執行之後,會產生以下訊息:
```
# Fill it with some values. First at the head
ERROR: Need to allocate and copy string for new queue element
l = [dolphin]
ERROR: Need to allocate and copy string for new queue element
l = [bear ih]
ERROR: Need to allocate and copy string for new queue element
```
因此將程式修正為動態分配所傳入字串長度 +1 大小的空間,並且利用 `strncpy()` 將 `s` 複製到新的空間。便可解決此問題。
```diff
element_t *element_new(char *s)
{
elemnt_t *new_element = malloc(sizeof(element_t));
+ if (!new_element)
+ return NULL;
+ int length = strlen(s) + 1;
+ char *str = malloc(sizeof(char) * length);
+ strncpy(str, s, length);
+ new_element->value = str;
- new_element->value = s;
return new_element;
}
```
最後透過 `list_add()` 函式將節點新增在佇列的開頭位置。
#### 如果 `*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` 。
> commit: [15275b1](https://github.com/duckcone/lab0-c/commit/15275b1eb2e592217a1ee6b65f9c6de33441f308)
在執行 `make test` 時,出現以下錯誤訊息:
```
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
Segmentation fault occurred. You dereferenced a NULL or invalid pointer--- trace-11-malloc 0/6
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
Segmentation fault occurred. You dereferenced a NULL or invalid pointer--- trace-12-malloc 0/6
```
:::danger
明確標示你參考的 GitHub 帳號名稱。
:::
判斷是 `element_new()` 函式出問題,在不知如何解決的情況下決定參考<s>其他人</s> 的方法,發現 2 個問題:
1. 其他人透過 `strdup()` 的方式將參數 `s` duplicate 一份,並且將節點的 `value` 指向其位址,因此我將原先的 `strncpy()` 更改為 `strdup()` 。
2. 在進行 `element_t *new_element = malloc(sizeof(element_t));` 之後並未判斷 `malloc()` 是否成功,因此加入判斷式,若 `malloc()` 失敗則釋放其記憶體並回傳 `NULL` 。
修改過後便可順利通過測試。
> commit:[15275b1](https://github.com/duckcone/lab0-c/commit/15275b1eb2e592217a1ee6b65f9c6de33441f308)
:::danger
allocate 的翻譯是「配置」,以區格 dispatch (分配/分發) 的翻譯。
:::
針對上述第 1 點,很明顯是我原先的<s>實做</s> 方式有問題。因此在重新檢視程式碼之後,發現我在 `char *str = malloc(sizeof(char) * length + 1);` 之後並未判斷是否<s>分配</s> 成功便進行 `strncpy()` ,因而導致 **dereferenced a NULL or invalid pointer** 的問題發生。在加入判斷式之後便解決了 Segmentation fault 的問題。
```diff sp
element_t *element_new(char *s)
{
int length = strlen(s) + 1;
char *str = malloc(sizeof(char) * length + 1);
+ if(!str) {
+ free(new_element);
+ free(str);
+ return NULL;
+ }
memset(str, '\0', length + 1);
strncpy(str, s, length);
```
### q_insert_tail()
:::danger
對照 Dictionary.com 對 [implement](https://www.dictionary.com/browse/implement) 一詞的解釋:
* to fulfill; perform; carry out:
* to put into effect according to or by means of a definite plan or procedure.
* Computers. to realize or instantiate (an element in a program), often under certain conditions as specified by the software involved.
* to fill out or supplement
「實作」會是符合上述 "implement" 語意的譯詞,取「起」以「創作」的意思,而「製造」本質上是固定輸入並通常產生固定輸出的行為。
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
與 `q_insert_head()` 的<s>實做</s> 實作方式相同,透過呼叫 `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.)
因此針對此函式進行修正。
> commit: [5f2037b](https://github.com/duckcone/lab0-c/commit/5f2037beadb855754af4418b6b3c4abb6708b5cf)
```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;
+ list_del(&head_element->list);
+
+ if (!sp)
+ return head_element;
+
+ memset(sp, '\0', bufsize);
+ strncpy(sp, head_element->value, bufsize);
+ INIT_LIST_HEAD(&(head_element->list));
return head_element;
}
```
### q_delete_mid()
`q_delete_mid()` 在實作上與 `q_remove_head()` 及 `q_remove_tail()` 有所不同,主要差異在於 `q_delete_mid()` 需找出佇列中間節點,並釋放其記憶體,而 `q_remove_head()` 及 `q_remove_tail()` 僅將佇列開頭或是結尾的節點移除,但並未釋放其記憶體。
利用兩個指標 `head_ptr` 以及 `tail_ptr` 對 `head` 的 `prev` 以及 `next` 方向逐一探訪,當兩個指標相等時即為佇列中間的節點。透過 `list_entry()` 取得節點並將其釋放。
> commit: [ba59182](https://github.com/duckcone/lab0-c/commit/ba591829e44a320b647f564046df8bb892143247)
### q_reverse()
由於原先的佇列是以雙向鏈結串列實作,因此在對佇列反轉時需考慮 `prev` 以及 `next` 兩個指標的操作。下列程式碼與附圖為詳細的實作方式。
```c
list_for_each_safe (current, safe, head) {
temp = current->next;
current->next = current->prev;
current->prev = temp;
}
```
<s>
![pic1](https://hackmd.io/_uploads/HysDBTe6p.png)
</s>
:::danger
使用 Graphviz 重新製圖,並嵌入到 HackMD 筆記中。
:::