# 2019q1 Homework1 (lab0) contributed by < [`leexun`](https://github.com/LeeXun) > ###### tags: 李洵, leexun ## 開發環境 ```shell $ uname -a Linux c2b1d6fd7613 4.9.93-linuxkit-aufs #1 SMP Wed Jun 6 16:55:56 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux $ gcc --version gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 2 On-line CPU(s) list: 0,1 Thread(s) per core: 1 Core(s) per socket: 1 Socket(s): 2 Vendor ID: GenuineIntel CPU family: 6 Model: 61 Model name: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz Stepping: 4 CPU MHz: 2697.971 BogoMIPS: 5395.94 L1d cache: 32K L1i cache: 32K L2 cache: 256K L3 cache: 3072K Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht pbe syscall nx pdpe1gb lm constant_tsc rep_good nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq dtes64 ds_cpl ssse3 sdbg fma cx16 xtpr pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch kaiser fsgsbase bmi1 avx2 bmi2 erms xsaveopt arat $ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=18.04 DISTRIB_CODENAME=bionic DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS" ``` ## C Programming Lab ### 程式實作與註解 #### queue_t ```=c typedef struct { list_ele_t *head; /* Linked list of elements */ list_ele_t *tail; /* 使 q_insert_tail 複雜度可為 O(1) */ int size; /* 使 q_size 複雜度可為 O(1) */ } queue_t; ``` #### q_new ```=c queue_t *q_new() { queue_t *q = malloc(sizeof(queue_t)); if (q == NULL) { // perror("q_new q malloc failed"); /* 這邊原本預期會有錯誤訊息,但是因為我們的 malloc 失敗是用 script 假裝的,所以 perror 會印出 Success 的字樣,所以我把後面的 perror 都註解起來了,不然 qtest 跑起來會很亂。*/ return NULL; } q->head = NULL; q->tail = NULL; q->size = 0; return q; } ``` #### q_free ```=c void q_free(queue_t *q) { if (q != NULL) { while (q->size != 0) { q_remove_head(q, NULL, 0); } free(q); } } ``` #### q_insert_head ```=c bool q_insert_head(queue_t *q, char *s) { if (q == NULL) { return false; } list_ele_t *newh; newh = (list_ele_t *) malloc(sizeof(list_ele_t)); if (newh == NULL) { return false; } newh->value = malloc(strlen(s) + 1); if (newh->value == NULL) { free(newh); /* 剛剛 malloc 的 newh 不用了*/ return false; } strcpy(newh->value, s); if (q->size == 0) { q->tail = newh; } newh->next = q->head; q->head = newh; q->size += 1; return true; } ``` #### q_insert_tail ```=c bool q_insert_tail(queue_t *q, char *s) { /* You need to write the complete code for this function */ /* Remember: It should operate in O(1) time */ if (q == NULL) { return false; } list_ele_t *newt; newt = malloc(sizeof(list_ele_t)); if (newt == NULL) { return false; } newt->value = malloc(strlen(s) + 1); if (newt->value == NULL) { free(newt); /* 剛剛 malloc 的 newt 不用了,不然不小心被用到會產生 segment fault */ return false; } strcpy(newt->value, s); if (q->size == 0) { q->head = newt; } else { q->tail->next = newt; } newt->next = NULL; q->tail = newt; q->size += 1; return true; } ``` #### q_remove_head ```=c bool q_remove_head(queue_t *q, char *sp, size_t bufsize) { if (q == NULL || q->size == 0) { return false; } if (sp != NULL) { strncpy(sp, q->head->value, bufsize - 1); sp[bufsize - 1] = '\0'; } list_ele_t *tmp = q->head; /* 要用一個 tmp 記住原本 head 的 addr,等下 head 換人後才能 free */ q->head = q->head->next; q->size -= 1; free(tmp->value); free(tmp); return true; } int q_size(queue_t *q) { /* 在 queue 的 struct 裡面增加 int size 紀錄大小,並在此使用*/ if (q == NULL) return 0; return q->size; } ``` #### q_reverse ```=c void q_reverse(queue_t *q) { if (q == NULL || q->size == 0 || q->size == 1) return; list_ele_t *cur = q->head->next; list_ele_t *next = cur->next; q->head->next = NULL; q->tail = q->head; while (next != NULL) { next = cur->next; cur->next = q->head; q->head = cur; cur = next; } } ``` #### 實作思路 - 首先跟著作業的提示,將 struct, q_new, q_size 修改,加上 size, tail。 - 因為已經有測項了,所以打算用類似 TDD 的方式做開發。 - new 好了之後的下一步,就是實作 insert,insert 有 insert_head, insert_tail 兩種,兩種的實作非常像,不過要注意在最後安插的順序。 - 插入寫好後因為 01 有 remove_head,所以繼續實作 remove_head。 - 寫好了跑了測資,沒過還噴了 segment fault,之前沒有使用 gdb 的經驗,查了一下別人是如何用來找 SF 的錯誤後並實作,才發現原來兩個 insert 在 new 有 malloc 成功但是 new->value 沒有 malloc 成功的時候,應該要把 new free 掉才行。 - 過了 01, 02, 08, 09, 10 後拿到 33/100 - 03 有 reverse 我還沒做,所以先跳過。 - 看看 04 為何沒過,才發現我沒有修改 free 裡面的實作。這樣是不是代表 01, 02 沒有做 q_free 呢?<b>看了 trace 裡面的 01, 02 指令之後,發現真的沒有測試 free</b>。 - 用 q_remove_head 實作出 q_free 之後,剩下 test 03, 05, 06, 07 沒過。 - reverse 的部分,原本使用了 prev, next, tmp 三個指標變數來實作,但是後來發現這樣 q->head 並沒有被修改到,所以把 prev 的部分用 q->head 來移動。 - reverse 過了之後剩下 06, 07 有 segment fault,這邊直接找了 `trace-06-string.cmd` 的指令來手動配合 gdb 試試看,結果發現是 next = cur->next 這邊 cur 是 NULL ptr,原來 reverse 不只在 q = NULL, q->size == 0 的時候不用做事情,我還忘記了在 q->size == 1 的時候也是不用做事的。也因為少了這個條件, cur 才有可能在做完一輪後會是 NULL 而且還沒有被 while(next != NULL) 的條件擋下來。 - 剩下 07 沒有過,也一樣複製整個 `trace-07-string.cmd` 的指令貼上到 qtest 裡面用 gdb 做 backtrace。結果發現是 size 指令的時候會噴錯,這邊就不用 trace 了,因為原本我只有把 q_size 直接回傳 q->size,完全沒有做檢查過濾。 - 加上 q == NULL 的 return 0 之後得到 100/100 ### 解釋自動評分系統運作的原理 - 最主要是有發現測項的指令都寫在 `./trace` 資料夾中,這樣在用 gdb 的時候可以直接複製貼上使用。 - TODO ### 提及 qtest 的行為和裡頭的技巧 - TODO - 閱讀 [0xff07](https://hackmd.io/s/SJnc7dtSN), [Naetw](https://hackmd.io/s/BysQssYHN) ### 額外心得 - 在寫 reverse 這樣的操作時,可以動手把圖畫下來,會比較好理解和思考。 - 這邊在處理 malloc 回傳 NULL 的時候,很好奇 Linux 是如何去撰寫這樣的錯誤處理。經過搜尋後,發現可以用 perror 去配合印出錯誤訊息,還有一些有趣的 marco,例如 [FAIL_IF](https://github.com/torvalds/linux/blob/master/tools/testing/selftests/powerpc/include/utils.h#L63-L71),使用 `__LINE__` 可以印出錯誤該行的行數。 - 關於測試時如何觸發 malloc 錯誤,想到以下兩種方式: 1. 故意 malloc 一個比 RAM 還要大的空間。 2. 去看一下測試 script 怎麼做的,TODO。 - 看到回傳值是 bool 覺得有點奇怪,C 印象中應該是用 _Bool,bool 應該是 C++ 的 type 才是。 :::danger 請參閱 C99 規格的 `<stdbool.h>`,詳閱並摘要相關內容 工程人員說話不要用「印象中」,不僅不專業,也是對自己所學的褻瀆,務必參照第一手材料 :notes: jserv ::: :::danger 收到!感謝老師的指教 :notes: leexun ::: - 修改 git commit 的編輯器 - `$ git config --global core.editor vim` - 被 git hooks 把 format 擋下來並修改完之後,要記得重新 git add 一次才會生效。 - 瀏覽同學的筆記學到 `strdup`,[點這邊](https://hackmd.io/s/HJ0Wv9dYm)有一些問題可以參考 - 學到 [perror](http://man7.org/linux/man-pages/man3/perror.3.html), [strerror](http://man7.org/linux/man-pages/man3/strerror.3.html) - 閱讀了 [0xff07](https://hackmd.io/s/SJnc7dtSN), [Naetw](https://hackmd.io/s/BysQssYHN) 的筆記後發現自己觀察的太淺了,會根據他們已經研究到了方向再做了解。 ### TODO - valgrind ``` $ valgrind ./qtest $ valgrind --leak-check=full ./qtest ```