# 2020q1 Homework1 (lab0) contributed by < `Tim096` > ## Outline [TOC] ## 環境 - [x] [GNU/Linux 開發工具](https://reurl.cc/KjXbQq) - [x] [Cppcheck](https://reurl.cc/2g8Zm6): 靜態程式碼分析工具 - [x] [VS-code(安裝C++的套件),並且用終端機執行](https://reurl.cc/ldZoe9)這邊同步的部分教的不清楚 - [x] [Vim](https://reurl.cc/MdX0Np):終端機,Vim 設定 - [ ] [Valgrind](https://valgrind.org/): 動態程式分析工具 - [x] [Git 教學和 GitHub 設定指引](/@sysprog/git-with-github) - [x] [HackMD -- LaTeX 語法與示範](/@sysprog/B1RwlM85Z?type=view) - [ ] [Makefile 語法和示範](/@sysprog/SySTMXPvl?type=view) - [ ] [Linux製圖工具:gnuplot](https://reurl.cc/ldZoDY) ## 作業事前準備 - [x] 良好的心態 - [x] lab0-c (當中有包含評分系統了) ## 作業要求 - [x] <font color="#f00">Q : circular linked list "" single linked list "" double linked list ? </font> A : 老師說要靠自己去發掘(也是一種訓練) 在 ``queue.[c/h]`` 中完成以下實作 * `q_new`: 建立新的「空」佇列; * `q_free`: 釋放佇列所佔用的記憶體; * `q_insert_head`: 在佇列開頭 (head) 加入 (insert) 給定的新元素 (以 LIFO 準則); - [x] <font color="#f00">Q : LIFO準則?Queue正常地從頭前面加入不是FIFO嗎 ? </font> A : CMD的文件是這樣寫的,但是其實有點問題 * `q_insert_tail`: 在佇列尾端 (tail) 加入 (insert) 給定的新元素 (以 FIFO 準則); - [x] <font color="#f00">Q : 相對誰來說 ?</font> A : CMD的文件是這樣寫的,但是其實有點問題 * `q_remove_head`: 在佇列開頭 (head) 移除 (remove) 給定的元素。 * `q_size`: 計算佇列中的元素數量。 * `q_reverse`: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列元素,換言之,它只能重排已經存在的元素; * `q_sort`: 以==遞增順序==來排序鏈結串列的元素 ## 實作流程 (好好看老師的文件) ### queue.h (以下想法參考別人的) 作者:[ZhuMon](https://hackmd.io/@ZhuMon/lab0-c) * 更改 `queue.h` 中的 `queue_t` ,使得 `q_size()` 以及 `q_insert_tail()` 能以 $O(1)$ 時間完成 * 依照作業教學,將 `queue.h` 中的 `queue_t` 增加成員 (`size`) * 此舉能讓 `q_size()` 藉由直接讀取 `queue_t` 中的 `size`,不必每次重新計算 queue 的大小 - [x] <font color="#f00">空間去換取時間</font> * 缺點是必須在每次讓 queue 新增或刪除成員時,管理 queue 的 size 以及 `tail` 指標,也讓 queue 所需的記憶體空間增大 `sizeof(pointer) + sizeof(int)` - [x] <font color="#f00">這樣用平攤分析 Amortized Analysis的方法算出來的 Time Complexity不就相同了嗎? 而且還是會浪費另外的記憶體空間</font> * 這裡只是提供一種方法並不代表是一定就好的 ```cpp= /* Queue structure */ typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; int size; /* Size of queue */ } queue_t; ``` ### queue.c * **q_new** * 在初始化 `queue_t` 前,先確認是否成功分配記憶體,避免操作到空指標 - [x] <font color="#f00">Q : malloc不是分配記憶體給他嗎? 為何這邊是說確認是否成功分配記憶體 ?</font> A : 這邊是指第4行,不是第3行 ```cpp= queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (q) { q->head = NULL; q->tail = NULL; q->size = 0; } return q; } ``` * **q_free** * 新增 `tmp` 作為釋放用的節點 * 藉由 `q->head` 的移動,遍歷整個 `queue`,並一一清除 > 本來想以 `q->head` 與 `q->tail` 是否相同作為結束迴圈的條件,後來發現這樣無法完全將 `queue` 清除,於是改為判斷 `q->head` 與 `q->tail`是否同時存在,且發現只要判斷 `q->head` 是否存在即可 ```cpp= void q_free(queue_t *q) { if (!q) //如果是空就直接回傳即可 return; list_ele_t *tmp = q->head; while (q->head) { q->head = q->head->next; tmp->next = NULL; free(tmp->value); free(tmp); tmp = q->head; } free(q); } ``` - [ ] <font color="#f00"> - [x] Q : 第10行不懂為何要free value,node free掉就好了不就好?</font> A : 對,沒有必要 > 確定沒有必要嗎XD,`tmp->value` 是一個 string 喔~ > [name=ZhuMon] * **q_insert_head** * 將新元素 (`list_ele_t`) 放入 queue 的前端 * 先判斷傳入的 `q` 是否為 `NULL`,避免分配記憶體後才發現 `q` 為 `NULL`,造成 memory leak * 在分配記憶體給新的元素後,判斷是否分配成功,若失敗,則將之前分配的 `newh` 清除 * 以 C library <`string.h`> 的 `strncpy` 複製 `s`,放入新元素的 `value` * 因為 `strncpy` 不會自動將其他位置設為 `\0`,所以使用 `memset` 將 `newh->value` 歸零 * 確保 `q->tail` 能夠正常運作,第一個 element 建立時,將 `q->tail` 指向 `q->head`,且隨著新增元素,位移到最後 ```cpp= bool q_insert_head(queue_t *q, char *s) { if (!q) return false; // Check separately to avoid extra malloc cause memory leak list_ele_t *newh; // newh means new element in head newh = malloc(sizeof(list_ele_t)); if (!newh) { return false; } // Allocate space and copy the string newh->value = malloc(sizeof(char) * (strlen(s) + 1)); if (!newh->value) { free(newh); return false; } memset(newh->value, '\0', strlen(s) + 1); strncpy(newh->value, s, strlen(s)); newh->next = q->head; q->head = newh; // first time if (!q->tail) { q->tail = q->head; } else { while (q->tail->next) { q->tail = q->tail->next; } } q->size += 1; return true; } ``` - [ ] <font color="#f00">Q : 第15行的 `* (strlen(s) + 1));` 這一部分不曉得為何需要 ? </font> A : 新版的CPP99其實已經不需要這樣了 - [ ] <font color="#f00">Q : 第16~18行如此判斷如果傳入Value=0是否會判斷錯誤 </font> A : - [ ] <font color="#f00">Q : 第20~21行為何不直接使用strcpy代替呢 ? </font> A : * **q_insert_tail** * 與 `q_insert_head` 差不多 * 將新元素 (`list_ele_t`) 放入 queue 的尾端 * 若 `queue` 為空 (`q->tail == NULL`),則將 `head` 和 `tail` 同時指向新元素 (`newt`) ```cpp= bool q_insert_tail(queue_t *q, char *s) { if (!q) return false; // Check separately to avoid extra malloc cause memory leak list_ele_t *newt; // newt means new element in tail newt = malloc(sizeof(list_ele_t)); if (!newt) return false; newt->value = malloc(sizeof(char) * strlen(s) + 1); if (!newt->value) { free(newt); return false; } memset(newt->value, '\0', strlen(s) + 1); strncpy(newt->value, s, strlen(s)); newt->next = NULL; if (!q->tail) { q->tail = q->head = newt; } else { q->tail->next = newt; q->tail = newt; } q->size += 1; return true; } ``` * **q_remove_head** - [ ] <font color="#f00">Q : 為何要傳入這麼多的參數,不是只要remove掉head這個東西嗎 ? </font> A : * 藉由確認 `q->head` 是否為 `NULL`,確認 `queue` 是否為空 * 若 `sp` 不為 `NULL`,必須將成功刪除後的字串複製進去,並且依照傳入的 `bufsize` 決定複製多少 * 比較 `bufsize` or 要刪除的字串長度 比較短,來決定真正要複製進 `sp` 的長度為何 * 呼叫 `memset` 時,需要保留最後一位來放 `\0`,所以 `realbufsize` 要加一 ```cpp memset(sp, '\0', realbufsize + 1) ``` * 若只複製部分字串(`bufsize`),由於呼叫 `memset` 會將 size += 1,而且複製完不能超過 `bufsize`,所以 `realbufsize = bufsize - 1` * 新增一個指標 `tmp` 以供釋放 (`free`) * 將 `tmp` 的成員都清空後,再釋放 `tmp` ```cpp= bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (!q || !q->head) { return false; } if (sp) { // Insure copy size is right size_t realbufsize = (bufsize > strlen(q->head->value)) ? strlen(q->head->value) : bufsize - 1; memset(sp, '\0', realbufsize + 1); strncpy(sp, q->head->value, realbufsize); } list_ele_t *tmp; tmp = q->head; q->head = q->head->next; tmp->next = NULL; free(tmp->value); free(tmp); q->size -= 1; return true; } ``` * **q_size** * 若 `q` 不存在,則返回 `0` * 藉由直接取得 `q->size`,達成在 $O(1)$ 時間內完成 `q_size()` * 在 `q_new` 時,便已將 `q->size` 設為 `0` ,因此就算 queue 為空,還是會返回 `0` ```cpp int q_size(queue_t *q) { return !q ? 0 : q->size; } ``` * **q_reverse** * 原本使用三個指標 (`prev`, `now`, `next`) 來反轉 queue * 一直思考如何將指標數量縮減,借鑑 [gpwork4u 的方式](https://hackmd.io/@gpwork4u/2020q1-hw-lab0c)後,發現可以藉由暫時不會用到的指標:`q->tail->next` 和 `q->head->next`,取代新的指標,只使用一個新的指標 `tmp` * 藉由檢查 `q->size < 2` 來確認 `queue` 是否只有一個或沒有元素 * <font color="#f00">此處的部份我不曉得先暫時多用一個只會會有何影響?因此我認為程式碼的易讀性更重要,因此我寫了一個Part2的版本</font> ```cpp= void q_reverse(queue_t *q) { // No effect if q is NULL or empty or only one element if (!q || q->size < 2) { return; } list_ele_t *tmp; tmp = q->head->next; q->tail->next = q->head; while (tmp != q->tail) { tmp = tmp->next; q->head->next->next = q->tail->next; q->tail->next = q->head->next; q->head->next = tmp; } q->tail = q->head; q->head = q->head->next; q->tail->next = NULL; } ``` - [ ] <font color="#f00">Q : 第4行`if (!q || q->size < 2)`中的`!q`是多餘的吧 ? (一開始即有定義 init=0)</font> A : * 下圖為進入 while 迴圈之前,queue 的狀況,分別用三種顏色(紫、綠、紅)代表主要控制的指標 ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; d [label="{ <data> d | <ref> }"]; a:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color="red"]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false]; c:ref:c -> d:data [arrowtail=dot, dir=both, tailclip=false]; d:ref:d -> a:data [arrowtail=dot, dir=both, tailclip=false, color="green"] head -> a; tmp ->b [color="purple"]; tail -> d; } ``` 1. 進入迴圈後,先將 `tmp` 指向下一個,避免待會將 `b->next` 反向後,無法取得 `c` 2. 接著藉由操作 `q->head->next->next`,將 `b->next` 反向,指向 `a` 3. 為了讓 `q->head->next` 前進,而不至於讓 `b` 無法取得,於是將 `tail->next` 指向 `b` 4. 將 `q->head->next` 指向 `tmp`,準備下次的反向 <pre> while (tmp != q->tail) { <span style="color:purple">tmp = tmp->next;</span> <span style="color:blue">q->head->next->next = q->tail->next;</span> <span style="color:green">q->tail->next = q->head->next;</span> <span style="color:red">q->head->next = tmp;</span> } </pre> ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; d [label="{ <data> d | <ref> }"]; a:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color="red", style="invis"]; a:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false, color="red"]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false, style="invis"]; b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false, color="blue"] c:ref:c -> d:data [arrowtail=dot, dir=both, tailclip=false]; d:ref:d -> b:data [arrowtail=dot, dir=both, tailclip=false, color="green", label=""] head -> a; tmp -> c [color="purple"]; tail -> d; } ``` <pre> while (tmp != q->tail) { <span style="color:purple">tmp = tmp->next;</span> <span style="color:blue">q->head->next->next = q->tail->next;</span> <span style="color:green">q->tail->next = q->head->next;</span> <span style="color:red">q->head->next = tmp;</span> } </pre> ```graphviz digraph linked_list{ rankdir=LR; node [shape=record]; a [label="{ <data> a | <ref> }"]; b [label="{ <data> b | <ref> }"]; c [label="{ <data> c | <ref> }"]; d [label="{ <data> d | <ref> }"]; a:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color="red", style="invis"]; a:ref:c -> d:data [arrowtail=dot, dir=both, tailclip=false, color="red"]; b:ref:c -> c:data [arrowtail=dot, dir=both, tailclip=false, style="invis"]; b:ref:c -> a:data [arrowtail=dot, dir=both, tailclip=false] c:ref:c -> d:data [arrowtail=dot, dir=both, tailclip=false, style=invis]; c:ref:c -> b:data [arrowtail=dot, dir=both, tailclip=false, color="blue"]; d:ref:d -> c:data [arrowtail=dot, dir=both, tailclip=false, color="green"] head -> a; tmp -> d [color="purple"]; tail -> d; } ``` :::warning 考慮以下變更: ```diff @@ -1,14 +1,13 @@ void q_reverse(queue_t *q) { - if (q == NULL || q->head == NULL) { + if (!q || !q->head) return; - } + list_ele_t *prev, *now, *next; prev = q->head; now = q->head->next; - if (now == NULL) { // there is only one element in queue + if (!now) /* only one element in queue */ return; - } next = q->head->next->next; @@ -18,7 +17,7 @@ prev->next = NULL; now->next = prev; - while (next != NULL) { + while (next) { prev = now; now = next; next = now->next; ``` 要點: 維持簡潔的縮排和風格 :notes: jserv ::: :::success 已更改程式碼為只剩一個新指標 ::: * **q_reverse_Part2** ```cpp= void q_reverse(queue_t *q){ if (q->size < 2) { return; } list_ele_t *pre = 0, *tmp = head, *next = head->next; while (next != 0) { tmp->next = pre; pre = tmp; // pre往後挪 tmp = next; // tmp往後挪 next = next->next; // next往後挪 } // next更新成NULL即跳出while loop tmp->next = pre; // 此時tmp位於最後一個node, 將tmp->next轉向 head = tmp; // 更新head為tmp } ``` * **q_sort** * 參考 [Linked LIst Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) * 使用 Merge sort 排序 * `merge_sort()` * 將一個 Linked list 拆成兩個 * 藉由兩個指標 (`l1`, `l2`) 不同速度的前進 * `l1` 會指向最後的 `element` * `l2` 會指向中間的 `element` * 將 `l1` 指向 `l2->next` 後,便可以得到兩個等長的 `linked list` (`head` 和 `l1`) * 將 `l2` 指向 `*head`,避免接下來使用 `*head` 後造成錯誤 * 將兩個已排序的 Linked list 合成一個 Linked list,並回傳 * 參考 [jwang0306](https://hackmd.io/@jwang0306/lab0-c) 的 `merge`,將比較字串的部分縮進 `while` * 藉由走訪 `l1` 和 `l2`,並比較兩者元素,來建立新 Linked list * 最後離開 `while` 不是剩下 `l1` 就是 `l2`,於是縮成一行 * `q_sort()` * 藉由 pointer to pointer 省略回傳 `q->head` ```cpp void merge_sort(**head) { if (!(*head) || !((*head)->next)) return; list_ele_t *l1 = (*head)->next; list_ele_t *l2 = *head; while (l1 && l1->next) { l2 = l2->next; l1 = l1->next->next; } l1 = l2->next; l2->next = NULL; l2 = *head; merge_sort(&l2); merge_sort(&l1); *head = NULL; list_ele_t **tmp = head; while (l1 && l2) { if (strcmp(l1->value, l2->value) < 0) { *tmp = l1; l1 = l1->next; } else { *tmp = l2; l2 = l2->next; } tmp = &((*tmp)->next); } *tmp = l1 ? l1 : l2; } void q_sort(queue_t *q) { // if q has only one element or q is empty, q->head == q->tail if (!q || q->head == q->tail) { return; } merge_sort(&q->head); while (q->tail->next) { q->tail = q->tail->next; } } ``` :::warning TODO: 1. 考量到程式碼變數和函式命名的風格 (作業說明有提及,採用 Linux 核心程式碼慣用風格),請修改上述程式碼,用小寫和精準的用字; 2. 程式碼尚可更精簡,請改寫; :notes: jserv ::: :::info 1. 沒有注意到參考的程式碼與當前的命名風格不同,已改正 2. 改寫程式碼 * 將原來的 `merge()` 以及 `mergeSortList()` 合併為 `merge_sort()` * ~~想將比較字串部分都縮進 `while` 迴圈內,但是還想不到辦法實現~~ * ~~雖然暫時無法再縮減程式碼~~,但是比較字串的 branch 數似乎也無法再降低 (更新) * 參考 [jwang0306](https://hackmd.io/@jwang0306/lab0-c) 藉由 pointer to pointer 將比較字串的部分縮進 `while` * 使用 pointer to pointer 省略回傳值 * 將使用的變數量縮減(重複使用變數) * 在 hackmd 上刪除部分註解,避免雜亂 ::: <font color="#f00">以上讀完但有問題</font> <font color="#f00">以下通通都還沒有讀完</font> ------------- ### natsort * 參考 [natsort](https://github.com/sourcefrog/natsort),將 `strcmp` 改為 `strnatcmp` > 發現在執行 `traces/trace-15-perf.cmd` 時,會因為時間超過,而測試失敗 * 於是確認 `strcmp` 與 `strnatcmp` 的需要時間。以 `time.h` 的 `clock` 函式計算時間 > 此為未使用任何 gcc 最佳化方法 (-O1 -O2 -O3 ...) 的狀況 ![](https://i.imgur.com/UBNHceJ.png) * 發現 `strnatcmp` 需要的時間在十萬次以上的情況下,與 `strcmp` 差距極大,造成 `qtest` Timeout :::warning 很好!你終於發現這個故意安插的細節。 需要留意到,glibc 裡頭的 strcmp 做了一系列最佳化,類似 [Speeding up string compare and sorting](http://mgronhol.github.io/fast-strcmp/) 的手段,但額外透過 SSE 和 AVX 指令集加速。相反地,[natsort](https://github.com/sourcefrog/natsort) 實作就很 naive (取「原始」和「單純」的意思),意味著有很大的加速空間,你可試著改進,記得對照 [CS:APP 第 5 章](https://hackmd.io/@sysprog/CSAPP-ch5) 來思考。 :notes: jserv ::: > 經由老師提示,我試著去 linux kernel 內尋找有使用到 natural sort 的部分,一開始我認爲應該跟 file system 有關,於是聚焦在 [linux/fs](https://github.com/torvalds/linux/tree/master/fs),粗略地尋找了一陣子卻沒有找到有關的資訊,但是在搜尋的過程中有發現 `sort` 這個命令 :::danger 1. 命令 (command) 和指令 (instruction) 不同,後者在「Linux 核心設計」課程特別指 CPU instruction,因此,`sort` 只能被稱「命令」,請變更用語; 2. Linux 核心的檔案系統主要維護 metadata (反映資料行為的資料),可參見 `i-node`,`sort` 就仰賴 metadata 進行排序,所以,討論的主體該釐清,不是 Linux 核心去「排序」檔案名稱,而是 coreutils 一類的套件提供 `sort` 工具來排序; :notes: jserv ::: > 1. 好的,已更改用語 > 2. 也就是說我一開始的找尋方向錯誤,Linux 核心不負責檔案的排序,「人類」看到的檔案系統順序都是由 coreutils 等套件來排序,所以我的確應該去閱讀 coreutils 的 `sort` 命令 :::warning 不能說「方向錯誤」,有好奇心是值得稱許的事。需要注意,排序不全然跟 Linux 核心無關,可參見 [The kernel and character set encodings](https://lwn.net/Articles/71472/),在核心內部的檔案系統實作,我們還是需要透過核心處理字元集 (如 Big5 [廣泛用於台灣], GB18030 [中國國家標準]),這樣後續在使用者層級的排序才有意義。 :notes: jserv ::: * 確認 `sort` 這個命令是否符合 natural sort * 發現 string 中含有空白時,兩者的行為會有差異 * sourcefrog 的 natural sort ``` pic01 pic2 pic 4 else pic 5 pic 7 ``` * macOS 的 `sort` 命令 > 後來發現應該使用 `$ sort -V` 來做排序 ``` pic 7 pic 4 else pic 5 pic01 pic2 ``` * 再去各種線上排序的網站實驗後,發現每個網站對於 natural sort 的定義也都不一樣 > 測試了 [Text Mechanic](https://textmechanic.com/text-tools/basic-text-tools/sort-text-lines/), [PineTools](https://pinetools.com/sort-list), [Gillmeister Software](https://gillmeister-software.com/online-tools/text/sort-list.aspx) * 閱讀 `sort` 命令的 man page 後,發現, `sort` 可以針對版本號碼做排序 ```shell $ ls pic* | sort -V pic01 pic2 pic 4 else pic 5 pic 7 ``` 效果符合 sourcefrog 的預期 * 稍微閱讀 [`sort` 命令](https://github.com/coreutils/coreutils/blob/master/src/sort.c) 的運行方式後,發現 `sort.c` 關於檔案、版本號碼的排序是由 [filevercmp](https://github.com/gagern/gnulib/blob/master/lib/filevercmp.c) 這個檔案來實作 * 實際與 strcmp、strnatcmp 比較 (開啟 `-O3` ) ![](https://i.imgur.com/rJSTqf9.png) 很明顯地看到 filevercmp 遠超其他兩者 > strcmp 所需時間一直只在 1 µs 附近,或許更低,但目前使用的 library 精準度只到微秒 * 另外,我還有發現字串越接近版本號(部分相似),strnatcmp 與 filevercmp 的表現越差 * TODO: 發現 `gnulib` 比較字串的方式效率也不盡人意後,決定先熟悉編譯器的最佳化等教材,之後再試著改進 ## Valgrind 實驗 > 運用 Valgrind 排除 qtest 實作的記憶體錯誤,並透過 Massif 視覺化 “simulation” 過程中的記憶體使用量,需要設計對應的實驗 ### 排解使用原配置可能造成的錯誤 * 使用 `make valgrind` 命令後,想要讓使用 `massif` 來視覺化記憶體使用量,可是卻發生錯誤 ```zsh $ valgrind --tool=massif ./qtest valgrind: Unknown option: --show-leak-kinds=all valgrind: Use --help for more information or consult the user manual. ``` * 由於在 macOS 和 Linux 都遇到相同問題,確認兩者的版本 * macOS 10.14.6 ``` $ valgrind --version valgrind-3.14.0.GIT ``` :::spoiler * 更新 macOS 的 Valgrind ``` $ brew upgrade valgrind ``` * 發生錯誤,要我更新 xcode,於是照著提示更新 xcode * 由於之前安裝 Ubuntu 環境一直失敗,以爲是 macOS 版本的問題,於是將系統從 High Sierra(10.13.6) 升級到 Mojave (10.14.6),所以 valgrind 是之前安裝的 * 升級完 xcode 後,想升級 Valgrind,發現 Valgrind not Found,試著安裝... ```shell $ brew install valgrind ... valgrind: This formula either does not compile or function as expected on macOS versions newer than High Sierra due to an upstream incompatibility. Error: An unsatisfied requirement failed this build. ``` * 只好試著以 github 安裝 參考 [此篇](https://stackoverflow.com/questions/52732036/how-to-install-valgrind-on-macos-mojave10-14-with-homebrew) ``` $ brew install --HEAD https://raw.githubusercontent.com/sowson/valgrind/master/valgrind.rb ``` * 安裝成功,可是版本竟然是 3.16.0 ? ``` $ valgrind --version valgrind-3.16.0.GIT ``` * 重新測試,發生同樣問題 ``` $ valgrind --tool=massif ./qtest valgrind: Unknown option: --show-leak-kinds=all valgrind: Use --help for more information or consult the user manual. ``` ::: * Ubuntu 19.10 ``` $ valgrind --version valgrind-3.15.0 ``` 查詢 [Valgrind 官網](http://valgrind.org/downloads/?src=www.discoversdk.com) 發現,最新版本的確是 3.15.0 * 重新閱讀 lab0-c 的 `README.md` 後,發現在 `.valgrindrc` 中有關於 Valgrind 的配置,並且找到該選項,將它刪除後,便可運作 * 發現 Valgrind 的另外一個配置: `--leak-check` 在 `.valrindrc` 裡的寫法為 `--memcheck:leak-check=full`,於是將 `--show-leak-kinds=all` 前也加上 `memcheck`,再執行,便通過了 * before ``` --show-leak-kinds=all ``` * after ``` --memcheck:show-leak-kinds=all ``` :::warning 請提交 `.valrindrc` 相關的 pull request :notes: jserv ::: :::success 已提交 ::: ### 設計實驗 * 目的: 想要知道 strnatcmp 與 filevercmp 的記憶體用量 #### 實驗一 * 讓兩者比較兩個不同字串 100000 次 * ![](https://i.imgur.com/Vzp3EKC.png) * TODO ## 論文 Dude, is my code constant time? > 研讀論文 Dude, is my code constant time?,解釋本程式的 “simulation” 模式是如何透過以實驗而非理論分析,達到驗證時間複雜度,需要解釋 Student’s t-distribution 及程式實作的原理; * 簡稱 `dudect` * TODO ## 獨立的終端機命令解譯器 > 解釋 select 系統呼叫在本程式的使用方式,並分析 console.c 的實作,說明其中運用 CS:APP RIO 套件 的原理和考量點。可對照參考 CS:APP 第 10 章重點提示 :arrow_forward: 為避免「舉燭」,請比照 qtest 實作,撰寫獨立的終端機命令解譯器 (可建立新的 GitHub repository) ### select 系統呼叫 * 在 [Linux manual page](http://man7.org/linux/man-pages/man2/select.2.html) 定義 select 如下 ```cpp /* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` * `select()` 可以監聽多個 file descriptors, * TODO ## Bonus 發現新通過的 Pull request 含有一些小bug,進行修改 * Add description of clang-format integration to vim ([#28](https://github.com/sysprog21/lab0-c/pull/28)) * 簡介:藉由在 vim 中新增 function,使得每次寫進 C/C++ 文件後,自動排版 ```shell function! Formatonsave() let l:formatdiff = 1 py3f <path to your clang-format.py>/clang-format.py endfunction autocmd BufWritePre *.h,*.cc,*.cpp call Formatonsave() ``` > 已發 Pull Request,也已通過 > Extend auto-indention for generic C/C++ source ([#29](https://github.com/sysprog21/lab0-c/pull/29)) > 簡介:新增 *.c 以及 *.hpp,使得 C/C++ 的檔案都適用自動排版 由於我的主系統是 macOS,他這個方法需要找到 `clang-format.py` 的位置,再取代 `<path to your clang-format.py>`,避免以後還需要再手動更新,於是寫了一個 script 將路徑寫好,並且 `export` 成環境變數 * .vimrc * 將 `clang-format.py` 的位置以 `$CLANG_FORMAT_PATH` 取代 ```shell ... function! Formatonsave() let l:formatdiff = 1 py3f $CLANG_FORMAT_PATH endfunction autocmd BufWrite *.h,*.hpp,*.c,*.cpp,*.c++ call Formatonsave() ... ``` * [setEnv.sh](https://github.com/ZhuMon/vimrc/blob/master/setEnv.sh) * 藉由 `uname` 命令,找出當前是在哪個作業系統 * 由於我存放 `setEnv.sh` 的資料夾裡,也有複製了老師的 `.clang-fomat` 檔案,所以之後不管在哪裡都會使用該設定進行排版 * 我是使用 ==zsh==,所以會將設定放到 `~/.zshrc` ```shell #!/bin/sh # Set environment of clang-format sysOS=`uname` if [ $sysOS = "Darwin" ]; then echo "On Mac OSX" echo "export CLANG_FORMAT_PATH to ~/.zshrc" echo "export CLANG_FORMAT_PATH=\"/usr/local/Cellar/clang-format/2019-05-14/share/clang/clang-format.py\"" >> ~/.zshrc echo "export clang_format_fallback_style=\"$PWD/.clang-format\"" >> ~/.zshrc elif [ $sysOS = "Linux" ]; then echo "On Linux" echo "export CLANG_FORMAT_PATH to ~/.zshrc" echo "export CLANG_FORMAT_PATH=\"/usr/share/vim/addons/syntax/clang-format.py\"" >> ~/.zshrc echo "export clang_format_fallback_style=\"$PWD/.clang-format\"" >> ~/.zshrc else echo "I don't know where clang-format.py is" fi ``` ## 自我檢討 - [ ] natural sort - [ ] 設計 Valgrind 實驗 - [ ] 讀論文 - [ ] 設計終端機命令解譯器 ###### tags: `進階電腦系統應用2020` `lab0`