# 2021q1 Homework1 (lab0)
contributed by < `Chungchiyu` >
###### tags: `linux2021`
## 作業簡介
本次作業是 Linux kernal 的第一次作業,目標是更深入的認識 C 語言,並且熟悉記憶體的操作。通過 lab0 的 queue 例題練習 Linked list 的撰寫並學習一些基本的指標操作。
作業過程中會運用到其他的工具,如:git 版本控制系統、GNU/Linux 開發工具等。利用這些工具可以熟練的寫程式,並檢查程式內容。
## 預期目標
- [x] queue.c 的完整實作
- [ ] make test 達到滿分
- [ ] 學習 Address Sanitizer
- [ ] 以 Valgrind 分析記憶體問題
- [ ] 持續更新程式
## 實作進度(更新中)
## 作業內容
### queue.c 實作
#### 內容
1. [q_new](#q_new)
2. [q_free](#q_free)
3. [q_insert_head](#q_insert_head)
4. [q_insert_tail](#q_insert_tail)
5. [q_remove_head](#q_remove_head)
6. [q_size](#q_size)
7. [q_reverse](#q_reverse)
8. [q_sort](#q_sort)
---
:::danger
如果只是列出程式碼,而沒有你的洞見和觀察,那就不算「開發紀錄」,請更正!
:notes: jserv
:::
#### `q_new`
:::spoiler 打開
```c=
queue_t *q_new()
{
/* allocate memory to q */
queue_t *q = malloc(sizeof(queue_t));
/* check if something goes wrong when allocating memory */
if (!q) {
return NULL;
}
/* initialize member in q */
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
:::
#### `q_free`
:::spoiler 打開
```c=
void q_free(queue_t *q)
{
/* Check if q is NULL */
if (!q)
return;
/* Steping by steps to free list_ele_t in q */
while (q->head) {
list_ele_t *tmp = q->head;
q->head = q->head->next;
free(tmp->value);
free(tmp);
}
free(q);
}
```
:::
#### `q_insert_head`
:::spoiler 打開
```c=
bool q_insert_head(queue_t *q, char *s)
{
/* check if q is NULL */
if (!q)
return false;
/* allocate memory to new list_ele_t for new head */
list_ele_t *newh = malloc(sizeof(list_ele_t));
//if alloctaion failed
if (!newh)
return false;
/* get the size of input char data, +1 for '\0' */
size_t value_size = strlen(s) + 1;
/* allocate memory for the input data */
newh->value = (char *) malloc(value_size * sizeof(char));
//if allocation failed
if (!newh->value) {
free(newh); //don't forget to free the memory that allocate for newh
return false;
}
/* copy input data into newh->value */
strncpy(newh->value, s, value_size);
/* insert newh into q*/
newh->next = q->head;
q->head = newh;
/* make head and tail the same if only one element in queue */
if (q->size == 0)
q->tail = newh;
q->size++;
return true;
}
```
:::
#### `q_insert_tail`
:::spoiler 打開
```c=
bool q_insert_tail(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newt = malloc(sizeof(list_ele_t));
if (!newt)
return false;
size_t value_size = strlen(s) + 1;
newt->value = (char *) malloc(value_size * sizeof(char));
if (!newt->value) {
free(newt);
return false;
}
//tail->next should be NULL
newt->next = NULL;
strncpy(newt->value, s, value_size);
/* if queue is empty just use ih to fill an element */
if (!q->head)
q_insert_head(q, s);
/* if not empty insert from tail */
else {
newt->next = q->tail->next;
q->tail->next = newt;
q->tail = newt;
q->size++;
}
return true;
}
```
:::
#### `q_remove_head`
:::spoiler 打開
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* if queue doesn't exist or empty*/
if (!q || !q->head)
return false;
/* if sp isn't NULL */
if (sp) {
/* if buffer size is lager than value's then let copySize to be value size, on the other way let copySize to be buffer size */
size_t copySize = (bufsize - 1 > strlen(q->head->value)
? strlen(q->head->value)
: bufsize - 1;)
sp[copySize] = '\0';
strncpy(sp, q->head->value, copySize);
}
/* free the origin head of queue and its value*/
list_ele_t *tmp = q->head;
q->head = q->head->next;
free(tmp->value);
free(tmp);
q->size--;
return true;
}
```
:::
#### `q_size`
:::spoiler 打開
```c=
int q_size(queue_t *q)
{
if (!q)
return 0;
/* return the size of queue */
return q->size;
}
```
:::
#### `q_reverse`
:::spoiler 打開
```c=
void q_reverse(queue_t *q)
{
/* if queue is empty or it has only one element */
if (!q || q->size < 2)
return;
/* three list elements are represent previous, now, and next node */
list_ele_t *prev = NULL, *now = q->head, *next = NULL;
/* reversing the nodes */
while (now != NULL) {
next = now->next;
now->next = prev;
prev = now;
now = next;
}
q->tail = q->head;
q->head = prev;
return;
}
```
:::
#### `q_sort`
:::spoiler {state=open} 打開
```c=
void q_sort(queue_t *q)
{
/* if queue is empty or has only onr element */
if (!q || q->size < 2)
return;
/* use mergesort to sort the queue */
mergeSort(&(q->head));
/* relocating tail position */
while (q->tail->next) {
q->tail = q->tail->next;
}
}
void mergeSort(list_ele_t **head)
{
if (!(*head) || !(*head)->next)
return;
list_ele_t *fast = (*head)->next;
list_ele_t *slow = *head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
slow = (*head);
mergeSort(&slow);
mergeSort(&fast);
while (slow && fast) {
int compare = strcasecmp(slow->value, fast->value);
if (compare == 0 ? strcmp(slow->value, fast->value) < 0 : compare < 0) {
(*head) = slow;
slow = slow->next;
} else {
(*head) = fast;
fast = fast->next;
}
head = &((*head)->next);
}
(*head) = slow ? slow : fast;
}
```
:::
### 遇到的問題(2021/03/17)
1. 在 mergeSort 中,我在最後排大小時一直遇到指向問題。原先我是參考 lab0 中的 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) ,我選擇不用 recursive 的方式來做 merge 而是用 pseudo node 來做,因為我覺得 recursive 會用到太多的 stack 記憶體,但分解還是用遞迴否則程式會太複雜。
```c=
merge_sort(&slow);
merge_sort(&fast);
//lines are the same with current's above
list_ele_t **ptr = NULL;
(*head) = NULL;
while (slow && fast) {
int compare = strcasecmp(slow->value, fast->value);
if (compare == 0 ? strcmp(slow->value, fast->value) < 0 : compare < 0) {
(*ptr)->next = slow;
slow = slow->next;
} else {
(*ptr)->next = fast;
fast = fast->next;
}
if (!(*head))
(*head) = (*ptr)->next;
(*ptr) = (*ptr)->next;
}
(*ptr)->next = slow ? slow : fast;
```
假設以下:
```graphviz
digraph mergeSort{
node[shape=record]
A [label="<f0> 1 |<f1> 13 |<f2> 17 |<f3> 9"]
B [label="<f0> 1 |<f1> 13"] C [label="<f0> 17 |<f1> 9"]
D [label="<f0> 1"] E[label="<f0> 13"] F[label="<f0> 17"] G[label="<f0> 9"]
A->B A->C
B:f0->D B:f1->E C:f0->F C:f1->G
}
```
此時當 recursive 的 mergeSort 已經分到這四個數並要排大小時。
(也就是程式碼到↓這幾段時)
```c=
fast = slow->next;
slow->next = NULL;
slow = (*head);
mergeSort(&slow);
mergeSort(&fast);
```
第一行 mergeSort 會取出 `slow->value = 1` 的 slow 位置
第二行 mergeSort 會取出 `fast->value = 13` 的 fast 位置
然後宣告一個指標的指標來存取指標位置,並先指向 NULL。再讓 head 也指向 NULL 作為辨識 head 是否有指向最小述職的位置。
然後進入以下迴圈:
```c=
while (slow && fast) {
int compare = strcasecmp(slow->value, fast->value);
if (compare == 0 ? strcmp(slow->value, fast->value) < 0 : compare < 0) {
(*ptr)->next = slow;
slow = slow->next;
} else {
(*ptr)->next = fast;
fast = fast->next;
}
if (!(*head))
(*head) = (*ptr)->next;
(*ptr) = (*ptr)->next;
}
```
首先,當 slow->value < fast->value 進入 `if` 、反之進入 `else`, 因為 1 > 13 進入 `if` ,此時: `(*ptr)->next = slow;`
```graphviz
digraph mergeSort_a{
node[shape=box]
ptr_next[label="ptr_next=slow"]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
ptr->ptr_next->1->NULL_1
fast->13->NULL_2
{rank=same;ptr ptr_next}
{rank=same;1 NULL_1 13 NULL_2}
}
```
然後:`slow = slow->next`
```graphviz
digraph mergeSort_b{
node[shape=box]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
ptr->ptr_next->1->NULL_1
fast->13->NULL_2
slow->NULL_1
{rank=same;ptr ptr_next}
{rank=same;1 NULL_1 13 NULL_2}
}
```
再來因為 `*head = NULL` 所以 `(*head) = (*ptr)->next`
```graphviz
digraph mergeSort_c{
node[shape=box]
ptr_next[label="head=ptr_next"]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
ptr->ptr_next->1->NULL_1
fast->13->NULL_2
slow->NULL_1
{rank=same;ptr ptr_next}
{rank=same;1 NULL_1 13 NULL_2}
}
```
接著 `(*ptr) = (*ptr)->next`
```graphviz
digraph mergeSort_d{
node[shape=box]
ptr[label="head=ptr=ptr->next"]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
ptr->1->NULL_1
fast->13->NULL_2
slow->NULL_1
{rank=same;ptr slow}
{rank=same;1 NULL_1 13 NULL_2}
}
```
最後:
```
(*ptr)->next = slow ? slow : fast;
```
```graphviz
digraph mergeSort_d{
node[shape=box]
ptr[label="head=ptr"]
ptr_next[label="ptr_next=fast"]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
ptr->1->13
ptr_next->13->NULL_2
slow->NULL_1
{rank=same;ptr ptr_next}
{rank=same;1 NULL_1 13 NULL_2}
}
```
:::warning
跳出這個 recursive 後會取得 head 的位置,程式錯誤。但我抓不出錯誤。
:::
2. 在 mergeSort 中,我在最後排大小時一直遇到指向問題,所以我借用了 [`Masa1u`](https://github.com/Masa1u/lab0-c/blob/master/queue.c) 的這段程式碼來改。假設以下:
```graphviz
digraph mergeSort{
node[shape=record]
A [label="<f0> 1 |<f1> 13 |<f2> 17 |<f3> 9"]
B [label="<f0> 1 |<f1> 13"] C [label="<f0> 17 |<f1> 9"]
D [label="<f0> 1"] E[label="<f0> 13"] F[label="<f0> 17"] G[label="<f0> 9"]
A->B A->C
B:f0->D B:f1->E C:f0->F C:f1->G
}
```
此時當 recursive 的 mergeSort 已經分到這四個數並要排大小時。
(也就是程式碼到↓這幾段時)
```c=
fast = slow->next;
slow->next = NULL;
slow = (*head);
mergeSort(&slow);
mergeSort(&fast);
```
第一行 mergeSort 會取出 `slow->value = 1` 的 slow 位置
第二行 mergeSort 會取出 `fast->value = 13` 的 fast 位置
然後進入以下迴圈:
```c=
while (slow && fast) {
int compare = strcasecmp(slow->value, fast->value);
if (compare == 0 ? strcmp(slow->value, fast->value) < 0 : compare < 0) {
(*head) = slow;
slow = slow->next;
} else {
(*head) = fast;
fast = fast->next;
}
head = &((*head)->next);
}
(*head) = slow ? slow : fast;
```
當 slow->value < fast->value 進入 `if` 、反之進入 `else`, 因為 1 > 13 進入 `if` ,此時: `(*head) = slow;`
```graphviz
digraph mergeSort_1{
node[shape=box]
head[label="head=slow"]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
head->1->NULL_1 fast->13->NULL_2
{rank=same;1 NULL_1 13 NULL_2}
}
```
然後: `slow = slow->next`
```graphviz
digraph mergeSort_2{
node[shape=box]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
slow->NULL_1
head->1->NULL_1 fast->13->NULL_2
{rank=same;1 NULL_1 13 NULL_2}
}
```
然後: `head = &((*head)->next);`
```graphviz
digraph mergeSort_3{
node[shape=box]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
head->NULL_1 1->NULL_1
slow->NULL_1 fast->13->NULL_2
{rank=same;1 NULL_1 13 NULL_2}
}
```
最後: `(*head) = slow ? slow : fast;`
```graphviz
digraph mergeSort_4{
node[shape=box]
NULL_1[label="NULL"]
NULL_2[label="NULL"]
head[label="head=fast"]
1->NULL_1 slow->NULL_1
head->13->NULL_2
{rank=same;1 NULL_1 13 NULL_2}
}
```
:::warning
跳出 `mergeSort(&slow);` 後使外一層的 `slow->value = 13`
但是原本的 1 就這樣消失了???
我就這樣一直卡在這裡,卡了幾十個小時,明明可以編譯通過並且跑測試出來也都是對的,但我邏輯上一直都想不通。
:::
:::info
心得:我光是寫第一個作業的第一個目標就花了不下幾十個小時,我深知自己基本功不足,光是處理最基本的課題就花了一堆時間,但是我還是想要學好這門課,所以只能慢慢來。
:::