# 2018q3 Homework2 lab(0) Queue
###### tags: `C語言`
contributed by < `asd757817` >
```
實驗環境c
作業系統: Ubuntu 16.04 x64
kernel: 4.15.0-34-generic
gcc 版本: gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)
```
**github 使用 develop 分支**
>請補上實驗環境
>[name=課程助教][color=red]
## int q_size
- 規定時間複雜度為 O(1)
- 可以推測出這個函式是直接回傳長度,所以必須在 queue list 的結構中加入儲存長度的變數。每次呼叫 insert 長度增一、呼叫 remove 時長度減一。
**queue_t 中加入 size 儲存 queue 長度**
```clike=
/\* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
/*
You will need to add more fields to this structure
to efficiently implement q_size and q_insert_tail
*/
int size;
} queue_t;
```
考慮 NULL queue 的情況,在 NULL queue 時直接回傳 0
```clike
int q_size(queue_t *q)
{
/* You need to write the code for this function */
/* Remember: It should operate in O(1) time */
if(q)
return q->size;
else
return 0;
}
```
**當創建一個 queue 時把長度設為0**
```clike=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
q->head = NULL;
q->size = 0;
return q;
}
```
## q_insert_head
- 規定執行成功回傳 true
- q 是 null 或不能配置記憶體則失敗回傳 faulse
- 配置空間儲存傳入的文字內容
```clike
bool q_insert_head(queue_t *q, char *s)
{
/* 判斷 q 跟 s 是否為 NULL */
if (q == NULL || s == NULL) {
return false;
}
/* 為新加入的節點配置空間、配置空間儲存 s 的內容 */
else {
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
/* What should you do if the q is NULL? */
int s_len = sizeof(s);
char *s_cpy = malloc(s_len);
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
/* 判斷是否為 NULL ,並把配置的空間釋放 */
if (newh == NULL || s_cpy == NULL) {
free(newh);
free(s_cpy)
return false;
} else {
strncpy(s_cpy, s, s_len);
newh->next = q->head;
newh->value = s_cpy;
q->head = newh;
q->size += 1;
return true;
}
}
###
}
```
在測試時出現 ==ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer== 的錯誤警告,檢查後發現程式在 malloc failure 時會對 newh、s_cpy 都釋放記憶體,但兩者至少有一個是 NULL 就造成了這個錯誤。
把程式修正成:
- 兩者都不為 NULL 才進行 insertion
- 檢查 newh、s_cpy 不為 NULL 就釋放記憶體
```clike
bool q_insert_head(queue_t *q, char *s)
{
if ( !q || !s ) {
printf("false");
return false;
} else {
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
char *s_cpy = malloc(sizeof(s));
/* What should you do if the q is NULL? */
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
if (newh && s_cpy) {
strcpy(s_cpy, s);
newh->next = q->head;
newh->value = s_cpy;
q->head = newh;
if ( !q->tail )
q->tail = newh;
q->size += 1;
return true;
} else {
if (newh)
free(newh);
if (s_cpy)
free(s_cpy);
return false;
}
}
}
```
## bool q_insert_tail
- 在 queue 的結尾加入新的物件
- 時間複雜度要求 O(1)
一般在結尾加入新的物件必須走訪至物件的 next 為 NULL 時,把 next 從 NULL 指向新的物件,但因為需要走訪整個 queue 所以時間複雜度為 O(n),為了在 O(1)找到 queue 結尾,因此需要新增一個指在 queue 結尾的指標。
修正 queue_t
```clike
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
bool q_insert_tail 大致上內容與 insert_head 相似,只有部份地方需要修改
- 新加入的物件由 tail 的 next 指向
- 若 queue 的 head 還是 NULL 時需把 head 設定成新加入的物件
```clike
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 */
/* 判斷傳入的兩個指標是否為 NULL */
if (q == NULL || s == NULL) {
return false;
} else {
/* 為新加入 queue 的物件配置空間、為新物件的 value 配置空間 */
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
int s_len = sizeof(s);
char *s_cpy = malloc(s_len);
/* 檢查配置空間是否成功 */
if (newh == NULL || s_cpy == NULL) {
free(newh);
free(s_cpy);
return false;
} else {
strncpy(s_cpy, s, s_len);
newh->next = NULL;
newh->value = s_cpy;
/* 若 head 為 NULL 表示目前 queue_size 為 0,此時加入的物件同時也是 head */
if (q->head == NULL)
q->head = newh;
/* 若 tail 不是 NULL 則把 tail->next 指向新加入的物件 */
if (q->tail != NULL)
q->tail->next = newh;
/* 把 tail 設成新加入的物件 */
q->tail = newh;
q->size += 1;
return true;
}
}
}
```
在 test 時會出現 ==ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer==
檢查後發現是判斷 newh、s_cpy 在釋放記憶體時出錯,原本是只要其中一個為 NULL 就釋放記憶體並回傳 false,但這樣當會對 NULL 的部份也進行一次 free ,應該改為其中一個為 NULL而另一個不為 NULL 的情況釋出不為 NULL 的記憶體才對
修改後的為
```clike
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 || !s) {
return false;
} else {
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
char *s_cpy = malloc(sizeof(s));
if( newh && s_cpy) {
//strncpy(s_cpy, s, s_len);
s_cpy = strcpy(s_cpy,s);
newh->next = NULL;
newh->value = s_cpy;
if (q->head == NULL) {
q->head = newh;
}
if (q->tail != NULL)
q->tail->next = newh;
q->tail = newh;
q->size += 1;
return true;
}
else{
if(s_cpy)
free(s_cpy);
if(newh)
free(newh);
return false;
}
}
}
```
## void q_free 將所有配置過的空間釋放
所有配置過的空間有:
- queue_t
- list_ele_t
- space for list_ele_t->value
```clike
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
list_ele_t *to_be_free = q->head; //把要 free 的對象指定成當前的 head
while(to_be_free){
q->head = q->head->next; // 走訪 queue 中的所有物件
free(to_be_free->value); // free value
free(to_be_free); // free list_ele_t
to_be_free = q->head; // 更新要 free 的目標
}
free(q); // free queue_t
}
```
## bool q_remove_head
```clike
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
/* 當 queue 是 NULL 時回傳 false */
if (q == NULL) {
return false;
}
/* 當 queue head 為 NULL 時回傳 NULL */
else if(q->head == NULL){
return false;
}else {
/* You need to fix up this code. */
/* 把原本 value 的內容複製到 sp */
list_ele_t *old_head = q->head;
int str_len = sizeof(old_head->value);
strncpy(sp, old_head->value, str_len);
/* 更新 queue head */
q->head = q->head->next;
/* 釋放原本 queue head 的空間 */
free(old_head->value);
old_head->value = NULL;
free(old_head);
old_head = NULL;
q->size -= 1;
return true;
}
}
```
在 make test 時一直出現錯誤訊息,目前已知是```strncpu```出現問題,但仍在處理中。
```bash
ERROR: Removed value meerkat_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX != expected value meerkat
Error limit exceeded. Stopping command execution
```
仔細重新閱讀了題目
:::info
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.)
:::
真正要取的字串長度是==bufsize-1==,最後一位要補成結尾符號('\0'),而 strncpy 只是將長度 n 的字串複製到目標中,**並沒有加入結尾符號**,因此我的作法:
- 把目標內 bufsize 長度都設成結尾符號再使用 strncpy 複製字串
修改後的 q_remove_head 如下:
```clike
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
if (!q) {
return false;
} else if (!q->head) {
return false;
} else {
/* You need to fix up this code. */
/* How about freeing the list elements and the strings? */
/* Free queue structure */
if (sp) {
memset(sp,'\0',bufsize);
strncpy(sp, q->head->value + '\0', bufsize-1);
printf("%s\n",sp);
}
list_ele_t *to_be_free = q->head;
q->head = q->head->next;
if (q->size == 1 && !q->head) {
q->tail = NULL;
}
free(to_be_free->value);
free(to_be_free);
q->size -= 1;
return true;
}
}
```
### void q_reverse
題目要求:
- 不能配置額外的記憶體空間
- 對於 NULL queue or empty queue 沒有影響
- 作法:
* 對 NULL queue 和 empty queue 不做任何事情
```clike
void q_reverse(queue_t *q)
{
/* You need to write the code for this function */
if( q && q->head){
q->tail = q->head;
list_ele_t *pre_node = NULL;
list_ele_t *next_node = q->head->next;
while(next_node){
q->head->next = pre_node;
pre_node = q->head;
q->head = next_node;
next_node = q->head->next;
}
q->head->next = pre_node;
}
}
```
## 評分機制相關
### Makefile 撰寫
- 以 "=" 或是 ":=" 賦予數值
- target: dependencies
* target 是要產生的目標物件,dependencies 是產生目標需要引入的物件,若找不到物件會在 makefile 中尋找是否有產生該物件的規則
* : 後以 tab 隔開,不是空格(空白鍵)
* all 是一個最終的目標,只有輸入 make 時,就是以產生 all 為目標進行動作
以這次作業的 makefile 為例子:
- 只輸入 make 指令是,程式的目標是產生 all 這個檔案,而產生這個檔案需要 $(GIT_HOOKS)、qtest,所以程式接著尋找產生這兩個檔案所需要的動作。
- 測試分數時會使用指令 make test,指定產生的目標檔案為 test,因此會執行
```
test: qtest scripts/driver.py
scripts/driver.py
```
### Git hook
是一個在執行 commit 或是 merge 指令時會觸發的一個腳本,又可以分成 pre-hook 或是 post-hook,一個是在指令輸入後執行前觸發;一個是在指令執行完畢後觸發。
這一次作業使用的是 pre-hook,當我們要進行提交的時候會觸發腳本,檢查這次提交之前是不是有執行過 clang-format 的排版檢查,而這次作業會使用的 clang-format、cppcheck 在腳本內也會檢查是否安裝,若是未安裝會以文字警告尚未安裝。
### Clang-format
調整 C 語言的原始碼的格式,以方便開發人員閱讀,由於不同的開發人員有不同的習慣,在團隊開發時為了使團隊間的溝通順利,可以利用 clang-format 調整程式碼的格式,原本套件就有預設Google、Chromium、Mozilla 等程式,開發人員也可以自行定義團隊的開發格式。
實際操作:
```clang-format -i target.c``` -i 參數會抓當前目錄底下的格式檔進行調整。
### cppcheck
一個靜態分析的工具,不用實際運行程式只透過原始碼分析找出程式的問題,所找的問題類型是編譯器難以察覺的。
檢查項目包括:
- double free
- 自動變量生存週期檢查
- 空指針引用
- sizeof內的計算
- 檢查動態配置是否被釋放
- ...等功能
在這次的作業中使用 cppcheck 檢查動態配置的記憶體是否有正確釋放、程式是否操作 null pointer 等,檢查可以編譯執行但卻有疑慮的問題。
### 評分機制
- qtest.c 是用來對 queue 進行操作的介面
- report.c 定義了對 queue 進行操作後**預期**得到的結果,若是結果和預期不符會輸出錯誤訊息
- harness.c 裡面定義了 malloc、free 記憶體的行為,用來測我們所做的是否有在正確的時間點進行 malloc、free
- 輸入指令 ```make test``` 可以測試程式並計算分數。在 Makefile 中得知這行指令會運行 driver.py 檔案。
- driver 檔案內引入了許多測試用的腳本(trace-01 ~ trace-01),在 trace 資料夾中可以看見每個腳本內對 queue 進行的操作,測試程式開啟 qtest並依據腳本內的指令進行操作,根據 report、harness 回傳的訊息給分。
### 效能改善
- 反轉陣列
* 由於原本是 queue 的結構是 singly linked list ,在做反向運算時需要額外紀錄當前節點、當前節點的前一個節點跟當前節點的下一個節點,才能完成反轉,但是若把結果修改成 doubly linked list 直接由節點本身就可以取得前一個節點與下一個節點,如此一來可以節省做反向運算的時間。