# 2020q1 Homework1 (lab0)
contributed by < `LuChungYing` >
:::danger
注意作業要求已清楚規範共筆寫作格式,請依循。從小處做起!
:notes: jserv
:::
說明
- [作業說明](https://hackmd.io/s/BJA8EgFB4)
- [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
## 實驗環境
```shell
$ uname -a
Linux lulu-GP62-6QE 5.3.0-40-generic #32~18.04.1-Ubuntu SMP Mon Feb 3 14:05:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
```
---
### 作業要求
在 `queue.[c/h]` 中完成以下實作
* `q_new`: 建立新的「空」佇列;
* `q_new`: 建立新的「空」佇列;
* `q_free`: 釋放佇列所佔用的記憶體;
* `q_insert_head`: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則);
* `q_insert_tail`: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則);
* `q_remove_head`: 在佇列開頭 (head) 移除 (remove) 給定的元素。
* `q_size`: 計算佇列中的元素數量。
* `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素;
* `q_sort`: 以遞增順序來排序鏈結串列的元素
### 實驗流程
1. 從 github fork 程式碼到自己的帳號
2. 使用 git clone 將檔案載到本地端
3. 開始修改程式碼
* 修改 *queue.h* 裡 structure queue_t,多加了 int size
```cpp
typedef struct {
list_ele_t *head;
/* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
* 修改 *queue.c* 裡 function q_size()
```cpp=
int q_size(queue_t *q)
{
return q->size;
}
```
做一次commit message: Change q_size return value to q->size
---
* 修改 ***queue.c*** function `q_new()`
檢查 `q` 是否為 NULL 是的話則直接回傳
否則做 queue 出初始,然後回傳 `queue_t` 的pointer
```cpp=
queue_t *q_new()
{
queue_t *q=malloc(sizeof(queue_t));
if (!q)
return NULL;
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
* 修改 ***queue.c*** 裡 funtion `q_free()`
* 檢查 `q` 是否為 NULL 是則直接回傳
* 否則以 `q->head` 當作紀錄點(紀錄free到哪裡)
* 再重頭free到最後
* 最後再 free `q`
```cpp=
void q_free(queue_t *q)
{
if (!q)
return;
while (q->head) {
list_ele_t *tmp;
tmp = q->head;
free(tmp->value);
free(tmp);
q->head = q->head->next;
}
free(q);
}
```
* 修改 ***queue.h*** 裡 structure `queue_t`
加入 `*tail`
```cpp=
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
做一次commit message: Change q_new() q_free() in .c, add tail in .h
---
* 修改 ***queue.c*** 裡 function `q_insert_head()`
* 先檢查q是否為NULL 是則直接回傳`false`
* 否則向 OS 請求兩塊記憶體 一塊為 element 的 pointer 一塊為 字串的空間
* 然後把 element 放在 `q->head`
* 如果 element 只有一個 表示一開始在放 所以要把 `tail` 指過去唯一的 element
```cpp=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
if (!q)
return false;
newh = (list_ele_t *) malloc(sizeof(list_ele_t));
if (newh == NULL)
return false;
newh->value = (char *) malloc(sizeof(char) * (strlen(s) + 1));
if (newh->value == NULL) {
free(newh);
return false;
}
strncpy(newh->value, s, strlen(s) + 1);
newh->next = q->head;
q->head = newh;
q->size++;
if (q->size == 1)
q->tail = newh;
return true;
}
```
* 修改 ***queue.c*** 裡 function `q_insert_tail()`
* 與 `q_insert_head()` 差不多 但是指的方向要注意
* `next` 是往後指的 所以新的指標的 `next` 要指向原本的 `tail`
```cpp=
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *newt;
if (!q)
return false;
newt = (list_ele_t *) malloc(sizeof(list_ele_t));
if (newt == NULL)
return false;
newt->value = (char *) malloc(sizeof(char) * (strlen(s) + 1));
if (newt->value == NULL) {
free(newt);
return false;
}
strncpy(newt->value, s, strlen(s) + 1);
newt->next = NULL;
q->tail->next = newt;
q->tail = newt;
q->size++;
if (q->size == 1)
q->head = newt;
return true;
}
```
* 修改 ***queue.c*** 裡 function `q_remove_head()`
* 先檢查 `q` 是否存在 還有element是否至少一個 否則回傳 `false`
* 檢查 sp 是否存在 是則 給它一塊記憶體空間 複製要刪除的字串給它 長度為 bufsize
* 需要一個 `tmp` 的 `list_ele_t` 指標 指向要刪除的 node
* 先把 `tmp` 裡的字串釋放 再釋放 `tmp`
```cpp=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if(!q)
return false;
if(q->size == 0)
return false;
if(sp){
sp = char*(malloc(sizeof(q->head->value)));
strncpy(sp, q->head->value, bufsize);
}
list_ele_t *tmp = q->head;
q->head = q->head->next;
tmp->next = NULL;
free(tmp->value);
free(tmp);
q->size--;
if(q->size == 0)
q->tail = NULL;
return true;
}
```
做一次commit message: Modify q_insert_head q_insert_tail q_remove_head
---
* 修改 ***queue.c*** 裡 function `q_reverse()`
* 方式是重頭(head)開始,把每一個 element 接到 `tail` 上 以 `tail->next` 和 `head->next` 當作兩個 tmp 依序的接到 head 前面 直到換到 `tail`
```cpp=
void q_reverse(queue_t *q)
{
if (q == NULL)
return;
if (q->size <= 1)
return;
list_ele_t *tmp;
q->tail->next = q->head;
while (q->head->next != q->tail) {
tmp = q->head->next;
q->head->next = tmp->next;
tmp->next = q->tail->next;
q->tail->next = tmp;
}
q->tail = q->head;
q->head = q->head->next;
q->tail->next = NULL;
}
```
* 修改 ***queue.c*** 裡 function `q_sort()`
* 無論在平均最佳最差的情況下時間複雜度都是 O(nlogn) 的 sort 就只有 mergesort 了
* 在 quiz1 有看到 mergesort 的 C code 但是發現它切 element 的方式是一個一個切,花費的時間複雜度是 O(n) 跟原先的 O(logn) 不一樣
* 我實做一個時間複雜度為 O(logn) 切法的mergesort,重點在於設兩個指標(fast, slow),一個指標以兩倍速的方式漸增到 tail ,一個則以一倍速的方式漸增,直到尾巴 slow指的剛好是第一個 list 最後一個位置, 然後在 fast = slow->next 就切分為二個 list了
* 然後以遞迴的方式切到剩一個 element
* merge 的部份比較簡單,以 `strcmp()` 就可以比較字串的大小,重點在於要宣告一塊記憶體空間供紀錄要回傳的指標
* 在測試的時候發現,一直跳出 `in merge()` 不可以 `malloc` ,不知道確切原因,所以我就先判斷第一個位置是L1 還是 L2,merge 完後回傳第一個位置
```cpp=
list_ele_t *merge(list_ele_t* l1, list_ele_t* l2)
{
if (l2 == NULL)
return l1;
if (l1 == NULL)
return l2;
list_ele_t *tmp = (list_ele_t*) malloc(sizeof(list_ele_t));
list_ele_t *re = tmp;
while (l1 != NULL && l2 != NULL){
if (strcmp(l1->value, l2->value) < 0){
tmp->next = l1;
l1 = l1->next;
}
else{
tmp->next = l2;
l2 = l2->next;
}
tmp = tmp->next;
}
if (l1)
tmp->next = l1;
if (l2)
tmp->next = l2;
list_ele_t *head = re->next;
free(re);
return head;
}
```
```cpp=
list_ele_t *mergesort(list_ele_t *h)
{
if (h == NULL || h->next == NULL)
return h;
list_ele_t *fast = h->next;
list_ele_t *slow = h;
while(fast != NULL && fast->next != NULL){
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *l1 = mergesort(h);
list_ele_t *l2 = mergesort(fast);
return merge(l1, l2);
}
void q_sort(queue_t *q){
if (!q)
return;
if (q->size <= 1)
return;
q->head = mergesort(q->head); }
```
做 make test 的時候 發現 insert_tail
要求 O(1) 未過, 查了一下其他人的作法,
在[有品味的code 14:20](https://www.ted.com/talks/linus_torvalds_the_mind_behind_linux#t-862314)有提到 需要使用 double pointer to the address of 最後一個 element 指的 `next`才可以在 insert_tail 不需要判斷是否 queue 為空 code 修改為:
```cpp=
newt->next = NULL;
*(q->indirect_tail) = newt;
q->indirect_tail = &newt->next;
q->size++;
if (q->size == 1)
q->head = newt;
return true;
```
透過 make test 除了 truncated strings
目前尚未得知怎麼做 其他都有達成
```
CC queue.o
LD qtest
scripts/driver.py -c
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
...
--- trace-17-complexity 5/5
--- TOTAL 94/100
```
## Valgrind
```
valgrind --tool=memcheck --show-possibly-lost=no ./qtest
```
利用動態偵測記憶體遺漏,發現除非我輸入尚未定義的指令會出現:
```
==4422== 32 bytes in 1 blocks are still reachable in loss record 1 of 25
==4422== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==4422== by 0x10B436: malloc_or_fail (report.c:189)
==4422== by 0x10BF48: add_cmd (console.c:123)
==4422== by 0x10C029: init_cmd (console.c:93)
==4422== by 0x10AC52: main (qtest.c:757)
```
的錯誤,裡面出現了 still reachale ,說明是library 函式的記憶體位置尚未釋放所導致
在 harness.c `(size_t) b` 的用意是,如果不轉的話,做指標位置的數字加減,會是加減這個指標指的位置大小的加減
```cpp
static size_t *find_footer(block_ele_t *b)
{
// cppcheck-suppress nullPointerRedundantCheck
size_t *p = (size_t *) ((size_t) b + b->payload_size + sizeof(block_ele_t));
return p;
}
```