# 2019q1 Homework1 (lab0)
contributed by < `yunchi0921` >
###### tags: `linux_kernel`
---
## 題目需求
* 實作 queue.c
* q_free : 釋放整個queue,包含其string
* q_insert_tail : 在尾端插入元素
* q_new : 新建一個queue
* q_remove_head : 從開頭端刪除一個元素
* q_reverse : 將queue反轉
* q_size : 計算queue中元素數量
## 實作
### `queue_t`
為了讓依題目要求`q_size` & `q_insert_tail`能再O(1)執行而新增一`tail`指標與`qsize`再插入與刪除時隨時紀錄。
```clike
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
*/
list_ele_t *tail; /*In order to let q_insert_tail operate
in O(1)
*/
size_t qsize;
} queue_t;
```
### `q_free`
因為要同時清除 string 與 list element 所以宣告`*cur`與`*prev`,每一次`while`迴圈中,`prev`指向`cur`位置,而`cur`指向下一個位址,由`prev`負責清除,直到`cur`指向`NULL`為止。
```clike
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
if (q != NULL) {
list_ele_t *cur = q->head;
list_ele_t *prev = NULL;
while (cur) {
prev = cur;
cur = cur->next;
free(prev->value);
free(prev);
}
}
free(q);
}
```
這裡藉由同學幫助發現當我`q`為NULL時,沒有返回return,則如果情況發生則會free到一個NULL的位置而產生問題。
修改如下
```clike
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
if (q != NULL) {
list_ele_t *cur = q->head;
list_ele_t *prev = NULL;
while (cur) {
prev = cur;
cur = cur->next;
free(prev->value);
free(prev);
}
}else
return;
free(q);
}
```
### `q_new`
根據註解判斷queue是否為空,若不是則初始化各值並回傳。
```clike
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
if (q) {
q->head = NULL;
q->tail = NULL;
q->qsize = 0;
} else
return NULL;
return q;
}
```
### `q_insert_head`
這裡值得注意的是tail的連接,實作方式是當queue的size為1時,就將tail指向新element。
```clike
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (!q) {
printf("quene is NULL\n");
return false;
} else {
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
if (newh) {
// including null character
newh->value = (char *) malloc(strlen(s) + 1);
if(newh->value)
strcpy(newh->value, s);
newh->next = q->head;
q->head = newh;
(q->qsize)++;
/*If only one element in queue ,
head and tail are on same element.
*/
if (q->qsize == 1)
q->tail = newh;
return true;
} else {
return false;
}
}
}
```
這邊也有一個問題,如果當`newh->value`配置失敗的時候,我應該`free(newh)`並return `false`,以避免memory leak。
修改如下,同時`q_insert_tail`也相同,不贅述直接修改程式碼。
```clike
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (!q) {
printf("quene is NULL\n");
return false;
} else {
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
if (newh) {
// including null character
newh->value = (char *) malloc(strlen(s) + 1);
if(newh->value)
strcpy(newh->value, s);
else{
free(newh);
return false;
}
newh->next = q->head;
q->head = newh;
(q->qsize)++;
/*If only one element in queue ,
head and tail are on same element.
*/
if (q->qsize == 1)
q->tail = newh;
return true;
} else {
return false;
}
}
}
```
### `q_insert_tail`
概念基本上跟q_insert_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 */
list_ele_t *newh;
/*If the q is NULL*/
if (!q) {
printf("queue is NULL\n");
return false;
} else {
newh = malloc(sizeof(list_ele_t));
if (newh) {
newh->value = (char *) malloc(strlen(s) + 1);
if(newh->value)
strcpy(newh->value, s);
else{
free(newh);
return false;
}
newh->next = NULL;
if (q->tail != NULL)
q->tail->next = newh;
q->tail = newh;
(q->qsize)++;
/*If only one element in queue ,
head and tail are on same element.
*/
if (q->qsize == 1) {
q->head = q->tail;
}
return true;
} else {
return false;
}
}
}
```
### `q_remove_head`
根據註解`sp`不為NULL時要將刪除之 string 複製到`sp`中,且別忘記清除 string 以及 list element。
```clike
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
list_ele_t *prev = NULL;
if (q->qsize != 0 && q != NULL) {
if (sp != NULL) {
memset(sp, '\0', bufsize);
strncpy(sp, q->head->value, bufsize - 1);
}
prev = q->head;
q->head = q->head->next;
/*
Free the element and the string
*/
free(prev->value);
free(prev);
(q->qsize)--;
return true;
} else {
return false;
}
}
```
這邊有一個問題需要注意
```clike
if (q->qsize != 0 && q != NULL)
```
題目要求說,當queue為空以及q為NULL的時候回傳false,但因為我`q->qsize`寫在前面的關係,compiler在當q為NULL時,會判定我去讀一個NULL pointer,故應該要寫成以下型式
```clike
if (q!= NULL && q->size != 0)
```
### `q_size`
題目要求在O(1)時間內執行,所以在`queue.h`更改`queue_t`新增`qsize`以達到此目的。
```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 != NULL)
return q->qsize;
else
return 0;
}
```
### `q_reverse`
這邊我參考[geeksforgeeks](https://www.geeksforgeeks.org/reverse-a-linked-list/)中3個指標的方式,裡頭有一個 gif 檔描述的相當清楚,但與我們不同的是,除了要改變 head ,同時也要改變 tail 。
```clike
void q_reverse(queue_t *q)
{
/* You need to write the code for this function */
if (q == NULL || q->qsize == 0)
return;
else {
q->tail = q->head;
list_ele_t *prev = NULL;
list_ele_t *cur = q->head;
list_ele_t *next = NULL;
while (cur != NULL) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
q->head = prev;
}
}
```
## `testmalloc`與`testfree`
感謝[0xff07](https://hackmd.io/s/SJnc7dtSN)詳細的解釋
這邊主要對`find_header` 及`find_free`看看有什麼想講的,以及許多不懂的問題。
```clike
static block_ele_t *find_header(void *p)
{
if (p == NULL) {
report_event(MSG_ERROR, "Attempting to free null block");
error_occurred = true;
}
block_ele_t *b = (block_ele_t *) ((size_t) p - sizeof(block_ele_t));
if (cautious_mode) {
/* Make sure this is really an allocated block */
block_ele_t *ab = allocated;
bool found = false;
while (ab && !found) {
found = ab == b;
ab = ab->next;
}
if (!found) {
report_event(MSG_ERROR,
"Attempted to free unallocated block. Address = %p",
p);
error_occurred = true;
}
}
if (b->magic_header != MAGICHEADER) {
report_event(
MSG_ERROR,
"Attempted to free unallocated or corrupted block. Address = %p",
p);
error_occurred = true;
}
return b;
}
```
首先`find_header`主要是藉由`block_ele_t`的大小來推算出header在哪邊,接下來則是一連串的檢查機制。
```clike
block_ele_t *ab = allocated;
bool found = false;
while (ab && !found) {
found = ab == b;
ab = ab->next;
}
if (!found) {
report_event(MSG_ERROR,
"Attempted to free unallocated block. Address = %p",
p);
error_occurred = true;
}
```
這邊`ab`指向`allocated`這個doubly-linked list的開頭,並且在迴圈中搜尋這個block到底有沒有再這個串列裡頭
```clike
if (b->magic_header != MAGICHEADER) {
report_event(
MSG_ERROR,
"Attempted to free unallocated or corrupted block. Address = %p",
p);
error_occurred = true;
}
```
這段程式我則不太明白,因為在`test_malloc`當中每個new_block的`magic_header`都會被指定成`MAGICHEADER`,這樣子與上一段的檢查,發現他已經是allocated的一個block,這樣子
`b->magic_header`一定會在`test_malloc`被指定為`MAGICHEADER`才對,有種重複檢查沒有必要感覺。
```clike
new_block->magic_header =MAGICHEADER;
```
我猜應該是因為 `cautious mode` 關閉的時候而存在的機制,不曉得正不正確。