###### tags: `linux2019`
# 2019q1 Homework1 (lab0)
contributed by < `ambersun1234` >
## 環境
```shell
$ uname -a
Linux 4.15.0-45-generic #48~16.04.1-Ubuntu SMP Tue Jan 29 18:03:48 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
$ gcc -v
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.11)
```
## 說明
+ [作業說明](https://hackmd.io/s/BJA8EgFB4)
+ [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
## 題目摘要
+ 實做一個可以支援 FIFO , LIFO 的 queue
+ 在 queue.h & queue.c 中修改以下 function
+ q_new: 新增一個空的 queue
+ q_free: 釋出所有被 queue 使用的資源
+ q_insert_head: 在 queue 的最前方新增一個新的資料( LIFO )
+ q_insert_tail: 在 queue 的最後方新增一個新的資料( FIFO )
+ q_remove_head: 移除最前方的資料
+ q_size: 取得目前 queue 的資料數量
+ q_reverse: 反轉 queue( 此 function 不應該新增或刪除任何 queue 裡面的資料 )
## 實驗紀錄
+ q_new
```c=1
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
if (q == NULL) {
return NULL;
} else {
q->head = NULL;
q->q_size = 0;
q->last = q->head;
return q;
}
}
```
+ malloc 之後必須要確定是否成功,所以要檢查回傳值是否為 NULL
+ q_free
```c=1
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
if (q != NULL) {
list_ele_t *current = q->head, *next;
while (current != NULL) {
next = current->next;
free(current->value);
free(current);
current = next;
}
free(q);
}
}
```
+ q_insert_head
```c=1
bool q_insert_head(queue_t *q, char *s)
{
if (q == NULL) {
return false;
}
list_ele_t *newh;
/* What should you do if the q is NULL? */
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 == NULL) {
return false;
} else {
newh->value = strdup(s);
if (newh->value == NULL) {
free(newh);
return false;
}
newh->next = q->head; // newh->next will definitely be head
if (q->head == NULL) {
// queue empty
q->last = newh;
}
q->head = newh;
q->q_size++;
return true;
}
}
```
+ newh->value 我原本是用 malloc 的方式實做,但是在後來的 free 以及 quit 中都寫到說類似 `ERROR: Freed queue, but 4 blocks are still allocated` 的東西。因此我參考了 [DyslexiaS 的開發紀錄](https://hackmd.io/s/Byjnik5F7),將 malloc 替換成 strdup 的方式實做才解決了問題
+ 還有就是在將新的節點插入進 queue 的時候我使用了較多的 if else 判斷導致程式碼過於冗長,[DyslexiaS 的開發紀錄](https://hackmd.io/s/Byjnik5F7) 也提供給我了一個另一種的解法,即將 newh->next 指向 q->head ( 因為 newh 必定會成為 queue 的第一個 )
+ q_insert_tail:
```c=1
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 q_size; /* Current linked list size */
list_ele_t *last; /* Store last node */
} queue_t;
```
+ `q_insert_tail` 要求 $O(1)$,因此在 `queue_t` 中新增了 `list_ele_t *last` 用以紀錄當前 queue 中最後一個元素
```c=1
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 *newh;
newh = malloc(sizeof(list_ele_t));
if (newh == NULL) {
return false;
} else {
newh->value = strdup(s);
if (newh->value == NULL) {
free(newh);
return false;
}
newh->next = NULL;
if (q->last == NULL) {
q->head = q->last = newh;
} else {
q->last->next = newh;
q->last = newh;
}
q->q_size++;
return true;
}
}
```
+ q_remove_head:
```c=1
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
if (q == NULL || q->head == NULL) {
return false;
} else {
if (sp != NULL) {
strncpy(sp, q->head->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
list_ele_t *temp = q->head;
q->head = q->head->next;
q->q_size--;
free(temp->value);
free(temp);
return true;
}
}
```
+ q_size:
```c=1
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 q_size; /* Current linked list size */
list_ele_t *last; /* Store last node */
} queue_t;
```
+ 為了達到 $O(1)$,我在 queue_t 中新增了 `int q_size` 紀錄了當前 queue 的 element 數量
```c=1
int q_size(queue_t *q)
{
/* You need to write the code for this function */
/* Remember: It should operate in O(1) time */
return q->q_size;
}
```
+ q_reverse:
```c=1
void q_reverse(queue_t *q)
{
/* You need to write the code for this function */
if (q == NULL) {
return;
}
list_ele_t *previous = NULL, *current = q->head, *next = q->head;
q->last = q->head;
while (next != NULL) {
// store
next = current->next;
// reverse
current->next = previous;
// move on
previous = current;
current = next;
}
q->head = previous;
}
```
## 自動評分系統運作的原理
+ 根據 Makefile 的內容可以得知,自動評分系統是使用了 python 來進行評分的
+ 最主要評分的 function 是 `Tracer` 裡面的 `run` & `runTrace`,評分的項目總共有15個,定義於 `Tracer` 裡面的 `traceDict` ; 其個別對應到的是 ./traces 的15個指令檔案
+ 在 `run` 中是先做一些初始化設定,然後接著用一個 for 迴圈跑 `traceDict` 的 keys,之後在呼叫 `runTrace`
```python=1
def run(self, tid = 0):
scoreDict = { k : 0 for k in self.traceDict.keys() }
print("---\tTrace\t\tPoints")
if tid == 0:
tidList = self.traceDict.keys()
else:
if not tid in self.traceDict:
print("ERROR: Invalid trace ID %d" % tid)
return
tidList = [tid]
score = 0
maxscore = 0
if self.useValgrind:
self.command = 'valgrind'
else:
self.command = self.qtest
self.qtest = ''
for t in tidList:
tname = self.traceDict[t]
if self.verbLevel > 0:
print("+++ TESTING trace %s:" % tname)
ok = self.runTrace(t)
maxval = self.maxScores[t]
tval = maxval if ok else 0
print("---\t%s\t%d/%d" % (tname, tval, maxval))
score += tval
maxscore += maxval
scoreDict[t] = tval
print("---\tTOTAL\t\t%d/%d" % (score, maxscore))
if self.autograde:
# Generate JSON string
jstring = '{"scores": {'
first = True
for k in scoreDict.keys():
if not first:
jstring += ', '
first = False
jstring += '"%s" : %d' % (self.traceProbs[k], scoreDict[k])
jstring += '}}'
print(jstring)
```
+ `runTrace` 中使用了 `subprocess.call` 呼叫子行程用以執行各個 `traceDict` 的檔案,若子行程執行中出錯,則 retcode 就不是0
```python=1
def runTrace(self, tid):
if not tid in self.traceDict:
print("ERROR: No trace with id %d" % tid)
return False
fname = "%s/%s.cmd" % (self.traceDirectory, self.traceDict[tid])
vname = "%d" % self.verbLevel
clist = [self.command, self.qtest, "-v", vname, "-f", fname]
try:
retcode = subprocess.call(clist)
except Exception as e:
print("Call of '%s' failed: %s" % (" ".join(clist), e))
return False
return retcode == 0
```
+ 最後評分是依據 `runTrace` 的回傳值來判斷要給幾分
```python=1
tval = maxval if ok else 0
```
maxval 是各個項目的最高分 ( t 為當前 traceDict 的 key )
```python=1
maxval = self.maxScores[t]
```
## qtest的行為和技巧
+ qtest 實際上是透過一層包裝來呼叫 function( q_insert_head , q_insert_tail , q_reverse ... etc. ),這些所謂的包裝是定義在 qtest.c裡面,如下所示
```c=1
bool do_new(int argc, char *argv[]);
bool do_free(int argc, char *argv[]);
bool do_insert_head(int argc, char *argv[]);
bool do_insert_tail(int argc, char *argv[]);
bool do_remove_head(int argc, char *argv[]);
bool do_remove_head_quiet(int argc, char *argv[]);
bool do_reverse(int argc, char *argv[]);
bool do_size(int argc, char *argv[]);
bool do_show(int argc, char *argv[]);
```
+ 而以上的這些 function 是以 function pointer 的形式透過 `add_cmd()` 儲存到 `單向` linked list 裡面,linked list 的設計是為了方便使用者自行增加 function 到互動界面上
+ 值得注意的是,在 `harness.h` 中有寫到
```c=1
#define malloc test_malloc
#define free test_free
#define strdup test_strdup
```
+ 意思就是說,該程式將這些 function 改成自己的版本,這個大概也就可以解釋為什麼我嘗試要 malloc newh->value 的時候會有錯誤
+ 更深入的閱讀程式碼之後我發現到,`test_malloc` 實際上是用一個更大的空間來儲存記憶體相關的東西,而且是使用 `雙向 linked list` 儲存
+ 以下這個 struct 是用來儲存記憶體相關的東西
```c=1
typedef struct BELE {
struct BELE *next;
struct BELE *prev;
size_t payload_size;
size_t magic_header; /* Marker to see if block seems legitimate */
unsigned char payload[0];
/* Also place magic number at tail of every block */
} block_ele_t;
```
在 test_malloc 裡面有一句是這麼寫的
`block_ele_t *new_block = malloc(size + sizeof(block_ele_t) + sizeof(size_t));`
可以發現到除了 block_ele_t 之外還有 size 跟 sizeof(size_t),其中 size 就是在 malloc 時的參數,另外一個是跟所謂的 `magic number` 有相關
> magic number: 程式設計中將一個數字寫死,用以表達特殊意義
+ 在 block_ele_t 中 magic_header 用以儲存此 struct 的記憶體狀態,回到真正 malloc 的地方,最後一個 `sizeof(size_t)` 即是要儲存 magic_footer 的
```c=1
/* Value at start of every allocated block */
#define MAGICHEADER 0xdeadbeef
/* Value when deallocate block */
#define MAGICFREE 0xffffffff
/* Value at end of every block */
#define MAGICFOOTER 0xbeefdead
```
+ `test_malloc` 的回傳值是定義成
`void *p = (void *) &new_block->payload;`
剛開始我很不明白為什麼是 payload ,而且他的大小還是0,後來有查到一個叫做 <font color=red>struct hack</font> 的東西
+ cf. `struct hack`
+ 使用這個方法可以使 struct 達到動態記憶體分配
+ 在 struct 的最後新增一個大小為0的陣列
+ 然後在 malloc 的時候要求空間除了原本 struct 之外再加上額外需求空間
> [reference struct hack](http://frankchang0125.blogspot.com/2013/01/c-struct-hack-structure-with-variable.html)
+ 以 struct hack 的方法即可以分配記憶體給 list_ele_t
+ 其中還有 `static size_t allocated_count = 0;` 用以紀錄目前 allocated 的記憶體數量