# 2020q3 Homework1 (lab0)
contributed by <`hsiehong`>
###### tags: `進階電腦系統理論與實作`
[作業要求](https://hackmd.io/@sysprog/2020-lab0)
## outline
[TOC]
實做[queue.c](https://github.com/sysprog21/lab0-c/blob/master/queue.c)以下功能
* `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`==: 「[進階電腦系統理論與實作](http://wiki.csie.ncku.edu.tw/sysprog/schedule)」課程所新增,以==遞增順序==來排序鏈結串列的元素,可參閱 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 得知實作手法;
---
## 開發紀錄
#### `q_new`
```c=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (q == NULL)
return NULL;
q->size = 0;
q->head = NULL;
q->tail = NULL;
return q;
}
```
* 若 malloc failed ,return `NULL` pointer
* 初始化 queue
#### `q_free`
```c=
void q_free(queue_t *q)
{
if (q == NULL)
return;
while (q->head) {
list_ele_t *tmp;
tmp = q->head;
q->head = q->head->next;
if (tmp->value)
free(tmp->value);
free(tmp);
}
free(q);
}
```
* 很直覺的方法,要注意每個 node 的 value 也要釋放記憶體
#### `q_insert_head`
```c=
bool q_insert_head(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
free(newh);
return false;
}
int len = strlen(s) + 1;
newh->value = (char *) malloc(len * sizeof(char));
if (!newh->value) {
free(newh);
return false;
}
strncpy(newh->value, s, len);
newh->next = q->head;
q->head = newh;
if (!q->tail)
q->tail = newh;
++q->size;
return true;
}
```
* 若 queue is `NULL` , 無法新增,回傳 `NULL` , 若 queue is empty , 更新 `q->head` , `q->tail` , `q->size`
* `newh->value` 要注意是新增 `strlen(s) + 1` 的大小,要保留 `\0` 的空間
* 依據 [CERN Common vulnerabilities guide for C programmers](https://security.web.cern.ch/recommendations/en/codetools/c.shtml) 建議而移除 strcpy 等不安全的函式 , 這裡使用strncpy()
* line20 原本是要處理 q->size == 0 的情況,後來覺得讀性不是很好,所以改成下面
```c
if (q->size == 0) {
q->head = newt;
} else {
q->tail->next = newt;
}
q->tail = newt;
```
:::warning
不懂 line 15,為什麼不是 `free(newh->value)` , 測資會過不了
:::
#### `q_insert_tail`
```c=
bool q_insert_tail(queue_t *q, char *s)
{
if (!q)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
if (!newt) {
free(newt);
return false;
}
size_t len = strlen(s) + 1;
newt->value = (char *) malloc(len * sizeof(char));
if (!newt->value) {
free(newt);
return false;
}
strncpy(newt->value, s, len);
newt->next = NULL;
if (!q->tail) {
q->head = newt;
} else
q->tail->next = newt;
q->tail = newt;
++q->size;
return true;
}
```
* 概念同 `q_insert_head`,換湯不換藥
* 同樣處理 q->size == 0 的特殊狀況且增加可讀性,line19-23 改成下面
```c
if (q->size == 0) {
q->head = newt;
} else {
q->tail->next = newt;
}
q->tail = newt;
```
#### `q_remove_head`
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head)
return false;
if (q->head->value)
strncpy(sp, q->head->value, bufsize);
list_ele_t *tmp = q->head;
q->head = q->head->next;
free(tmp->value);
free(tmp);
--q->size;
return true;
}
```
* 移除當前 head node 並更新 head node,`q->size` - 1
* 若 head node value 不為空,將值複製到 `*sp`
#### `q_size`
```c=
int q_size(queue_t *q)
{
if (q != NULL)
return q->size;
else
return 0;
}
```
#### `q_reverse`
```c=
void q_reverse(queue_t *q)
{
if (!q || !q->head)
return;
list_ele_t *curr, *pre, *nxt;
curr = q->head;
pre = NULL;
q->head = q->tail;
q->tail = curr;
while (curr) {
nxt = curr->next;
curr->next = pre;
pre = curr;
curr = nxt;
}
}
```
* 參考[文章](https://www.geeksforgeeks.org/reverse-a-linked-list/)作法
* 若 queue 為 `NULL` 或 empty 不用動作
#### `q_sort`
```c=
void q_sort(queue_t *q)
{
if (!q || q->size == 1)
return;
list_ele_t *curr, *pre, *tmp, *tail;
tail = NULL;
while (q->head != q->tail) {
curr = pre = q->head;
while (curr && curr->next && curr->next != tail) {
if (strcmp(curr->value, curr->next->value) > 0) {
tmp = curr->next;
curr->next = tmp->next;
tmp->next = curr;
if (curr == q->head) {
q->head = tmp;
pre = tmp;
} else {
pre->next = tmp;
pre = pre->next;
}
} else {
if (curr != q->head)
pre = pre->next;
curr = curr->next;
}
}
tail = curr;
}
}
```
* 參考 [文章](https://npes87184.github.io/2015-09-12-linkedListSort/) 作法,這裡選擇 [bubble sort](https://zh.wikipedia.org/wiki/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F),不過不意外測資全過不了 (TLE) QQ
依據上面程式碼第一次跑完的成績,有排序的部份都沒過
`--- TOTAL 71/100`
----
改成 [merge sort](https://en.wikipedia.org/wiki/Merge_sort),也是參考上篇[文章](https://npes87184.github.io/2015-09-12-linkedListSort/)作法
```c=
list_ele_t *merge_2_list(list_ele_t *l1, list_ele_t *l2)
{
list_ele_t *n_head = NULL;
list_ele_t **tmp = &n_head;
while (l1 && l2) {
if (strcmp(l1->value, l2->value) > 0) {
*tmp = l2;
l2 = l2->next;
} else {
*tmp = l1;
l1 = l1->next;
}
tmp = &((*tmp)->next);
}
if (l1)
*tmp = l1;
else
*tmp = l2;
return n_head;
}
```
* 參考 [`ZhuMo`](https://hackmd.io/@cccccs100203/linux2020-lab0) 同學的,使用到 pointer to pointer
```c=+
list_ele_t *merge_sort(list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *fast, *slow;
fast = head->next;
slow = head;
// split list into 2 half list
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
fast = slow->next;
slow->next = NULL;
slow = head;
list_ele_t *left = merge_sort(slow);
list_ele_t *right = merge_sort(fast);
return merge_2_list(left, right);
}
```
```c=+
void q_sort(queue_t *q)
{
if (!q || q->size <= 1)
return;
// sorting by merge sort
q->head = merge_sort(q->head);
q->tail = q->head;
while (q->tail && q->tail->next)
q->tail = q->tail->next;
}
```
* 排序完得更新新的 `q->tail`
* 大多數測資都能通過 `--- TOTAL 94/100`
## Valgrind 記憶體分析
執行 `make valgrind`
結果:`--- TOTAL 94/100`
問題在 test06
```
+++ TESTING trace trace-06-string:
# Test of truncated strings
```
錯誤訊息如下
```
==7368== 32 bytes in 1 blocks are still reachable in loss record 1 of 30
==7368== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7368== by 0x10B490: malloc_or_fail (report.c:189)
==7368== by 0x10BFA2: add_cmd (console.c:123)
==7368== by 0x10C083: init_cmd (console.c:93)
==7368== by 0x10ACAC: main (qtest.c:759)
```
* 看到 `truncated strings`,直覺告訴我是 remove head 的部份出問題了,回去看了一次程式碼如下,還有 spec ,發現是字串結尾沒有忘記加上 `null terminator`
```c
if (!q || !q->head)
return false;
if (q->head->value)
strncpy(sp, q->head->value, bufsize);
else
return false;
```
修改如下
```c
if (!q || !q->head)
return false;
if (q->head->value) {
strncpy(sp, q->head->value, bufsize-1);
sp[bufsize - 1] = '\0';
} else
return false;
```
test06過了,但換 test09 錯誤如下
```
# Test remove_head with NULL argument
==9611== Invalid write of size 1
==9611== at 0x4C3331F: __strncpy_sse2_unaligned (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9611== by 0x10CD7A: strncpy (string_fortified.h:106)
==9611== by 0x10CD7A: q_remove_head (queue.c:124)
==9611== by 0x109F10: do_remove_head_quiet (qtest.c:430)
==9611== by 0x10B992: interpret_cmda (console.c:220)
==9611== by 0x10BF06: interpret_cmd (console.c:243)
==9611== by 0x10C4D4: cmd_select (console.c:569)
==9611== by 0x10C71C: run_console (console.c:628)
==9611== by 0x10AE41: main (qtest.c:772)
==9611== Address 0x0 is not stack'd, malloc'd or (recently) free'd
```
原來沒注意到 `sp` 也有可能是 `NULL`,判斷條件補上
```c
...
if (q->head->value && sp)
...
```
這樣就沒問題了
```
--- TOTAL 100/100
```
## Massidf 視覺化記憶體使用量
`$ valgrind --tool=massif ./qtest`
利用第 16 筆測資來做範例,測試腳本如下
```
# Test performance of sort with random and descending orders
# 10000: all correct sorting algorithms are expected pass
# 50000: sorting algorithms with O(n^2) time complexity are expected failed
# 100000: sorting algorithms with O(nlogn) time complexity are expected pass
option fail 0
option malloc 0
new
ih RAND 10000
sort
reverse
sort
free
new
ih RAND 50000
sort
reverse
sort
free
new
ih RAND 100000
sort
reverse
sort
free
```
```
$ valgrind --tool=massif ./qtest < traces/trace-16-perf.cmd
```
```
$ massif-visualizer massif.out.<PID>
```
![](https://i.imgur.com/7ilDJja.png)
* 可以看到 `memory heap consumption` 的使用量是隨著 item 的數目升高的,並且可以看到每次 free 完 queue 記憶體幾乎都會被釋放,但有個問題是第一次釋放完跟第二次釋放完 queue 記憶體並不是回到相同的狀態,這部份還不知道怎麼回事。而程式結束時 `memory heap consumption` 也隨著歸零
## TODO :
* ~~Valgrind 記憶體分析~~
* ~~Massif 視覺化記憶體使用量~~
* 設計相關實驗,查看記憶體使用狀態
* 論文閱讀 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)