# 2018q3 Homework2 (lab0)
contributed by < [wingard](https://github.com/wingard) >
###### tags: `HW2`
## 作業進度表
- [x] 在 GitHub 上 fork [lab0-c](https://github.com/sysprog21/lab0-c)
* 參閱 [GitHub 設定指引](http://wiki.csie.ncku.edu.tw/github)
- [x] 詳細閱讀 [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
* (英文閱讀正是我們想強化的議題),依據指示著手修改 `queue.[ch]` 和連帶的檔案,測試後用 Git 管理各項修改。
* 在提交程式前,務必詳閱 [如何寫好 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/s/rJwiDHGKQ)」,增添開發紀錄和 GitHub 連結,提及如何逐步達到要求,以及如何改善效能
* 解釋自動評分系統運作的原理
* 提及 `qtest` 的行為和裡頭的技巧
## 實驗環境
```
$ gcc --version
gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.1 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/
legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic
```
## [如何寫好 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/) -- 重點摘要
* 好的 commit messages 的 7 個規則: [Source](https://hackmd.io/s/SJvojkBtG#Git-commit-messages)
1. Separate subject from body with a blank line
標題與內文以一個空白行分隔,這樣可在使用 `$ git log` 閱讀時較易區分標題
2. Limit the subject line to 50 characters
標題字數最多 50。因為標題字數超過 50 在 github 上會被隱藏到 `…` 裡面,需要額外點開才能完整知道這個 commit 在做什麼。並且超過 50 字可能就不是一個精簡的標題
3. Capitalize the subject line
標題首字大寫
4. Do not end the subject line with a period
標題結尾不要句點
5. Use the imperative mood in the subject line
標題用命令口吻
6. Wrap the body at 72 characters
內文一行字數不超過 72
這樣在 `$ git log` 裡面看會比較清楚,排版比較好看
7. Use the body to explain what and why vs. how
內文說明是什麼、為什麼而不是如何做。因為看 commit 的人是想知道你為什麼要做這件事,而不是要知道你如何做這件事的 (動機比細節更重要)
### 標題
* 一個正確的 Git commit 標題應該要能夠讓完成下面的句子,使之完整:
* If applied, this commit will *你的標題*
舉例而言:
If applied, this commit will **refactor subsystem X for readability**
If applied, this commit will **update getting started documentation**
If applied, this commit will **remove deprecated methods**
If applied, this commit will **release version 1.0.0**
If applied, this commit will **merge pull request #123 from user/branch**
* 如果你很難總結出標題,這代表你可能在一個 commit 裏面做了太多的改變。請儘量讓 commits 原子化 (atomic commits)。
### 內文
* 在大多數的情況下,你可以省略掉描述這些變更的細節。程式碼本身就已經能自我說明了 (如果程式碼太複雜,必須要有說明的話,這時候就是程式碼本身的註解啦)。你只需要專注在解釋做出這樣改變的原因 ─ 事情在改變前為什麼可行 (以及他的錯誤),他們目前運作的狀況,以及為什麼你決定要以這個方法解決問題。
---
## [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
### Overview
* queue_t Structure
* singly-linked list
* Note: 在只有一個 element 時,head 與 tail 都會指向此 element

* NULL queue: A NULL queue is one for which the pointer has value NULL.
* `q==NULL`
* Empty queue: An empty queue is one pointing to a valid structure, but the head field has value NULL.
* `q->head = NULL`
### queue_t struct 內容修改
* 增加 tail pointer 欄位
* 增加 number of elements 欄位
```C=1
/* Queue structure */
typedef struct {
list_ele_t *head; /* pointer to head */
list_ele_t *tail; /* pointer to tail */
unsigned int elementNum; /* Number of elements */
} queue_t;
```
## Programming Task
### q_new: Create a new, empty queue.
* 註解有提到 Return NULL if could not allocate space ,故 malloc 失敗時可以直接 return NULL pointer,malloc 成功時對 q 作初始化
```C=1
/*
Create empty queue.
Return NULL if could not allocate space.
*/
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
if (q != NULL)
{
q->head = q->tail = NULL;
q->elementNum = 0;
}
return q;
}
```
* 參考 [Stackoverflow 的討論](https://stackoverflow.com/a/605858/500983) ,由於 malloc 回傳的 pointer 由於已經是 generic pointer `void *`,故不需要再做轉型
```C
queue_t *q = malloc(sizeof(queue_t)); //OK
queue_t *q = (queue_t *) malloc(sizeof(queue_t)); // Cast is not required
```
### q_free: Free all storage used by a queue.
* 先確認 q 不是 null pointer
* 根據 malloc manpage ,傳 NULL pointer 進去 free() 不會有問題,但為了減少內層的處理成本,在外層先作判斷
> The free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc(). Otherwise, or if free(ptr) has already been called before, undefined behavior occurs. **If ptr is NULL, no operation is performed.**
* 使用 *n 暫存當前 element 指向的下一個 element ,分別 free element 指向的 string 以及 element 自身後,將 *n 回存回 *pos
* 概念類似 [linked list 和非連續記憶體操作](https://hackmd.io/s/SkE33UTHf) 課程提到的 [list_for_each_safe](https://github.com/torvalds/linux/blob/master/include/linux/list.h#L472)
```C=1
/* Free all storage used by queue */
void q_free(queue_t *q)
{
if (!q)
{
return;
}
list_ele_t *pos = q->head;
while(pos)
{
list_ele_t *n = pos->next;
free(pos->value); /* free string */
free(pos); /* free list element */
pos = n;
}
/* Free queue structure */
q->elementNum = 0;
free(q);
}
```
### q_insert_head: Attempt to insert a new element at the head of the queue (LIFO discipline).
* 基本操作
* 將新增的 `element->next` 指向當前的 head element
* 將 `q->head` 指向此 element
* 若 insert 時 queue 為 NULL queue ,則 `queue->tail` 必為 `NULL`
* 將 `queue->tail` 也指向此 element
* 最後將 `queue->element` 數目加 1
```C=1
/*
Attempt to insert element at head of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
*/
bool q_insert_head(queue_t *q, char *s)
{
if (!q)
{
return false;
}
list_ele_t *newh = malloc(sizeof(list_ele_t));
if (!newh)
{
return false;
}
/* Don't forget to allocate space for the string and copy it */
newh->value = malloc(strlen(s)+1);
if (newh->value == NULL)
{
free(newh);
return false;
}
memcpy(newh->value, s, strlen(s)+1);
newh->next = q->head;
q->head = newh;
if (!q->tail)
{
q->tail = newh;
}
q->elementNum++;
return true;
}
```
### q_insert_tail: Attempt to insert a new element at the tail of the queue (FIFO discipline).
* 基本操作
* 原先 `q->tail` element的 next 指向此新增的 element
* 將 `q->tail` 指向此 element
* 新增的 `element->next = NULL`
* 若 insert 時 queue 為 NULL queue ,則 `queue->head` 必為 `NULL`
* 將 `queue->head` 也指向此 element
* 最後將 `queue->element` 數目加 1
```C=1
/*
Attempt to insert element at tail of queue.
Return true if successful.
Return false if q is NULL or could not allocate space.
Argument s points to the string to be stored.
The function must explicitly allocate space and copy the string into it.
*/
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *newt;
if (!q)
{
return false;
}
newt = malloc(sizeof(list_ele_t));
if (!newt)
{
return false;
}
/* Don't forget to allocate space for the string and copy it */
newt->value = malloc(strlen(s)+1);
if (!newt->value)
{
free(newt);
return false;
}
memcpy(newt->value, s, strlen(s)+1);
if (q->tail)
{
q->tail->next = newt;
}
q->tail = newt;
newt->next = NULL;
if (!q->head)
{
q->head = newt;
}
q->elementNum++;
return true;
}
```
:::info
一開始作 q_insert_head() 與 q_insert_tail() 內的 allocate space for the string 時,是透過 strdup() ,但跑自動測試時會失敗,百思不得其解...
後來閱讀了 [pjchiou](https://hackmd.io/s/HkTJUodY7#%E7%82%BA%E4%BB%80%E9%BA%BC%E4%B8%8D%E8%83%BD%E7%94%A8-strdup) 的共筆,才知道是 harness.c 的實作,對於 malloc() 與 free() 會導向自行定義的 test_malloc() 與 test_free(),如果呼叫 strdup() 會變成 malloc() 對應 test_free(),變成對不上而導致自動測試失敗
```C=1
/* Tested program use our versions of malloc and free */
#define malloc test_malloc
#define free test_free
```
:::
### q_size: Compute the number of elements in the queue.
* 練習 ternary operator
```C=1
/*
Return number of elements in queue.
Return 0 if q is NULL or empty
*/
int q_size(queue_t *q)
{
return q? q->elementNum; 0;
}
```
### q_remove_head: Attempt to remove the element at the head of the queue.
* 指標 ptr 指向要移除的 head element
* 如果要移除的 `element->next` 為 `NULL` ,表示此時 queue 中只有一個 element ,移除 element 後需要同時將 `q->tail` 設為 `NULL`
```C=1
/*
Attempt to remove element from head of queue.
Return true if successful.
Return false if queue is NULL or empty.
If sp is non-NULL and an element is removed, copy the removed string to *sp
(up to a maximum of bufsize-1 characters, plus a null terminator.)
The space used by the list element and the string should be freed.
*/
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head) // NULL queue or empty queue
{
return false;
}
list_ele_t *ptr = q->head;
q->head = q->head->next;
if (!q->head) //case for only one element in the queue
{
q->tail = NULL;
}
if (sp)
{
memcpy(sp, ptr->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
free(ptr->value);
free(ptr);
q->elementNum--;
return true;
}
```
### q_reverse: Reorder the list so that the queue elements are reversed in order.
* 最難的一個實作,看了 `pjchiou` 的共筆才理解
* 使用三個指標去暫存 前一個/目前/下一個 element
* 三個指標同時往前移動一步後,將當前 element->next 指向前一個 element
```C=1
/*
Reverse elements in queue
No effect if q is NULL or empty
This function should not allocate or free any list elements
(e.g., by calling q_insert_head, q_insert_tail, or q_remove_head).
It should rearrange the existing ones.
*/
void q_reverse(queue_t *q)
{
if (!q || !q->head) { // NULL or empty
return;
}
list_ele_t *head = q->head;
list_ele_t *tail = q->tail;
list_ele_t *pos = q->head;
list_ele_t *prev;
list_ele_t *n = pos->next;
while (n) {
prev = pos;
pos = n;
n = n->next;
pos->next = prev;
}
q->head = tail;
q->tail = head;
q->tail->next = NULL;
}
```
---
## 程式碼格式檢查(clang-format)
* git add 時如果 source code 格式不符合規定的話,會跳出指示要求透過 `clang-format -i <source code>` 來修改格式
* 參考了 [Kernel.org 上的 clang-format 簡介](https://www.kernel.org/doc/html/v4.17/process/clang-format.html),透過 clang-format 可以將程式碼整理成統一格式,確保大家同步開發以及 code review 時,能有相同的 coding style
---
## 自動評分系統
* 從 [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) 可得知,`$make test` 與 `scripts/driver.py` 有相同效果,可以從 Makefile 中得到證實:
```
test: qtest scripts/driver.py
scripts/driver.py
```
* 輸入`-h` 可以看使用說明
* -h: 印出
* -v: 印出訊息的資料量大小( 類似debug message level )
*
```
$ ./scripts/driver.py -h
Usage: ./scripts/driver.py [-h] [-p PROG] [-t TID] [-v VLEVEL]
-h Print this message
-p PROG Program to test
-t TID Trace ID to test
-v VLEVEL Set verbosity level (0-3)
```
* help message 少提到一個 `-A` ,
---
## qtest 之行為與技巧
* 設計一個 switch 來接受 command arguments
```C=1
while ((c = getopt(argc, argv, "hv:f:l:")) != -1) {
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'f':
strncpy(buf, optarg, BUFSIZE);
buf[BUFSIZE - 1] = '\0';
infile_name = buf;
break;
case 'v':
level = atoi(optarg);
break;
case 'l':
strncpy(lbuf, optarg, BUFSIZE);
buf[BUFSIZE - 1] = '\0';
logfile_name = lbuf;
break;
default:
printf("Unknown option '%c'\n", c);
usage(argv[0]);
break;
}
}
```
* queue_init()
*
* init_cmd();
* console_init();
* set_verblevel(level);