# 2020q3 Homework1 (lab0)
###### tags: `sysprog2020`
contributed by < [`Msiciots`](https://github.com/Msiciots/lab0-c) >
[**作業說明**](https://hackmd.io/@sysprog/2020-lab0)
## :penguin: 作業要求
* 在 GitHub 上 fork [lab0-c](https://github.com/sysprog21/lab0-c)
* 參閱 [Git 教學和 GitHub 設定指引](https://hackmd.io/@sysprog/git-with-github) ^附教學影片^
* ==詳細閱讀 [C Programming Lab](https://www.cs.cmu.edu/afs/cs/academic/class/15213-s20/www/labs/cprogramminglab.pdf)== ,依據指示著手修改 `queue.[ch]` 和連帶的檔案,測試後用 Git 管理各項修改,記得也該實作 `q_sort` 函式。
* 在提交程式變更前,務必詳閱 [如何寫好 Git Commit Message](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/)
* 不用理會 [Autolab](http://www.autolabproject.com/) 和檔案下載的描述,這兩者都是 CMU 專屬
* 除了修改程式,也要編輯「[作業區](https://hackmd.io/@sysprog/2020-homework1)」,增添開發紀錄和 GitHub 連結,除了提及你如何逐步達到自動評分程式的要求外,共筆也要涵蓋以下:
* 運用 Valgrind 排除 `qtest` 實作的記憶體錯誤,並透過 Massif 視覺化 "simulation" 過程中的記憶體使用量,需要設計對應的實驗
* 研讀論文 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf),解釋本程式的 "simulation" 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 [Student's t-distribution](https://en.wikipedia.org/wiki/Student%27s_t-distribution) 及程式實作的原理。注意:現有實作存在若干致命缺陷,請討論並提出解決方案;
* 挑戰題
* 參照 [antirez/linenoise](https://github.com/antirez/linenoise),強化 `qtest` 命令列功能,使其支援單行或多行編輯、歷史命令處理、命令自動補完 (例如輸入 `h` 之後按下 Tab 按鍵,自動接續補上 `elp`,成為完整的 `help` 命令)。除了整合程式碼外,應當說明 [antirez/linenoise](https://github.com/antirez/linenoise) 的運作原理,注意到 [termios](http://man7.org/linux/man-pages/man3/termios.3.html) 的運用
* 思考如何預設開啟 AddressSanitizer,卻又能滿足效能測試所需 (可能需要兩種以上的 build target)
* 指出現有程式的缺陷 (提示: 和 [RIO 套件](http://csapp.cs.cmu.edu/2e/ch10-preview.pdf) 有關),嘗試強化並提交 pull request
* 截止日期:
* Sep 20, 2020 (含) 之前
* 越早在 GitHub 上有動態、越早接受 code review,評分越高
## 開發紀錄
### 開發環境
- Ubuntu 20.04.1 LTS
- gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0
### `queue.h`
首先滿足 `q_insert_tail` 與 `q_size` 須達到 $O(1)$ 時間複雜度的要求,新增指向串列尾端的指標 (tail) 與表示串列元素個數的 (size) ,方便在後續操作中使用。
```cpp
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
int size;
} queue_t;
```
### `queue.c`
#### q_new
- 檢查初始化是否分配記憶體成功,若失敗則將錯誤訊息輸出到 `stderr`
```cpp
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (!q)
fprintf(stderr, "malloc() failed: malloc() return NULL\n");
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
#### q_free
- 參考[第一周上課內容](https://hackmd.io/@sysprog/c-linked-list?type=view),使用指標的指標移動節點
- 新增 `*tmp` 釋放節點記憶體
```cpp
void q_free(queue_t *q)
{
if (!q)
return;
list_ele_t **indirect = &q->head;
while (*indirect){
list_ele_t *tmp = *indirect;
indirect = &(*indirect)->next;
free(tmp->value);
free(tmp);
}
free(q);
}
```
#### q_insert_head & q_insert_tail
兩者功能相似,以 `q_insert_head` 作為舉例
```cpp
bool q_insert_head(queue_t *q, char *s)
{
if (!q || !s)
return false;
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
if (!newh) {
fprintf(stderr, "malloc() failed: newh in q_insert_head().\n");
return false;
}
newh->value = strdup(s);
if (!newh->value) {
fprintf(stderr, "strdup() failed: new->value in q_insert_head().\n");
free(newh);
return false;
}
newh->next = q->head;
q->head = newh;
// If newh is the first element.
if (!q->tail)
q->tail = newh;
// Increase amount of elements.
q->size++;
return true;
}
```
每次分配記憶體 (`maclloc()`, `strdup()`) 後檢查,若失敗則印出錯誤訊息並釋放已分配記憶體空間 (`newh`)。
`insert_head`的動作視覺化如下圖:
`newh->next = q->head;`
```graphviz
digraph add_entry {
rankdir=LR;
node [shape=record];
head [label= "q-head"];
tail [label= "q-tail"];
n1 [label="{ <data> | <ref> }"];
n2 [label="{ <data> | <ref> }"];
n3 [label="{ <data> | <ref> }"];
new [label="{ <data> | <ref> }",color=Red];
new:ref:c -> n1:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
head-> n1;
tail -> n3;
n1:ref:c -> n2:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
n2:ref:c -> n3:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
n3:ref:c -> NULL [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
}
```
`q->head = newh;`
```graphviz
digraph add_entry {
rankdir=LR;
node [shape=record];
head [label= "q-head"];
tail [label= "q-tail"];
n1 [label="{ <data> | <ref> }"];
n2 [label="{ <data> | <ref> }"];
n3 [label="{ <data> | <ref> }"];
new [label="{ <data> | <ref> }",color=Red];
new:ref:c -> n1:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
head-> new;
tail -> n3;
n1:ref:c -> n2:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
n2:ref:c -> n3:data [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
n3:ref:c -> NULL [arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
}
```
如果 `q->tail` 不存在代表第一次新增節點,`q->head`, `q->tail` 一起指向 `newh`
```cpp=
if (!q->tail)
q->tail = newh;
```
```graphviz
digraph add_entry {
rankdir=LR;
node [shape=record];
head [label= "q-head"];
tail [label= "q-tail"];
new [label="{ <data> | <ref> }",color=Red];
head-> new;
tail -> new;
new:ref:c -> NULL[arrowhead=vee, arrowtail=dot, dir=both, tailclip=false];
}
```
#### q_remove_head
```cpp=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head)
return false;
list_ele_t *rm = q->head;
if (sp && rm->value) {
strncpy(sp, rm->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
q->head = q->head->next;
free(rm->value);
free(rm);
q->size--;
return true;
}
```
利用 `rm` 指到欲刪除的節點,再將節點內容複製到 `sp`,隨後進行刪除。
#### q_size
```cpp=
int q_size(queue_t *q)
{
return q ? q->size : 0;
}
```
如果 `q` 存在直接回傳`q->size`(初始值為 0)
#### q_reverse
```cpp=
void q_reverse(queue_t *q)
{
if (!q || !q->head || !q->head->next)
return;
list_ele_t *pre = NULL;
list_ele_t *cur = q->head;
list_ele_t *next;
q->head = q->tail;
q->tail = cur;
while (cur) {
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
}
```
`if (!q || !q->head || !q->head->next)` 在節點為空或只有一個節點時不需 reverse ,直接回傳。利用指標 `pre`, `cur` , `next` 依序紀錄前一個節點、當前節點和下一個節點進行串列反轉。
#### q_sort
```cpp=
void q_sort(queue_t *q)
{
if (!q || q->size <= 1)
return;
q->head = merge_sort(q->head);
while (q->tail->next) {
q->tail = q->tail->next;
}
}
```
使用 merge sort 實作排序演算法,找到串列中間節點分成兩份再進行 merge_sort(Divide),之後合併回傳陣列(Conquer)。
```cpp=
list_ele_t *merge_sort(list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *fast = head->next;
list_ele_t *slow = head;
// split list
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *l1 = merge_sort(head);
list_ele_t *l2 = merge_sort(fast);
return merge(l1, l2);
}
```
利用 `strcasecmp()` 比對串列大小,`head` 指到合併後串列的開頭,`*tmp` 指到當前新增的節點
```cpp=
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
if (!l1)
return l2;
if (!l2)
return l1;
list_ele_t *head = NULL;
list_ele_t **tmp = &head;
/* sort list elements based on strnatcmp */
while (l1 && l2) {
if (strcasecmp(l1->value, l2->value) < 0) {
*tmp = l1;
l1 = l1->next;
} else {
*tmp = l2;
l2 = l2->next;
}
tmp = &((*tmp)->next);
}
if (l1)
*tmp = l1;
if (l2)
*tmp = l2;
return head;
}
```
## [Valgrind](https://valgrind.org) 程式執行分析
```
make valgrind
```
利用作業中事先配置好的 `valgrind` 檢查記憶體錯誤,從[執行輸出](https://github.com/Msiciots/lab0-c/blob/master/valgrind-output/make-valgrind)初步沒有發現問題(`malloc() & strdup() failed: ...` 是我 `queue.c` 程式自行輸出的錯誤訊息)
### 透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗
Todo
## 研讀論文 [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)
Todo