# 2019q3 Homework2 (lab0)
contributed by < `uccuser159` >
## 開發環境
作業系統:Ubuntu 18.04.2 LTS
---
## Overview
* 在 [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) 中要使用 Linked list 來實作 Queue
在`queue.h` 有宣告兩個結構:
```cpp
/* Linked list element */
typedef struct ELE {
char *value;
struct ELE *next;
} list_ele_t;
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
} queue_t;
```
`value` 會指向字串的一個指標
`next` 會指向下一個 linked-list
`head` 為 queue 中的第一個 linked-list
定義 ==NULL queue== 和 ==empty queue== :
NULL queue 表示沒有 queue
empty queue 表示的是一個空的 queue
在`queue.c`中要執行下列這些 task:
- **q_new**:製造出一個新的 queue (empty queue)
- **q_free**:將 queue 所佔的記憶體給釋放掉
- **q_insert_head** : 在 queue 的 head 插入元素( LIFO )
- **q_insert_tail** : 在 queue 的 tail 插入元素( FIFO )
- **q_remove_head** : 從 queue 的 head 移除元素
- **q_size** : 回傳目前 queue 的大小
- **q_reverse** : 把 queue 做反轉
---
## 實作
* 為了確認 `q_insert_tail` 和 `q_size` 的時間複雜度為 $O(1)$ ,所以在`queue.h`中加入指向 queue 尾端的 pointer 以及儲存 size 大小。
```cpp
typedef struct {
list_ele_t *head;
list_ele_t *tail;
int size;
} queue_t;
```
* **q_new**
```cpp
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? */
if (q == NULL)
return NULL;
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
```
* **q_free**
```cpp
void q_free(queue_t *q)
{
/*No effect if q is NULL*/
if (q == NULL)
return;
/* How about freeing the list elements and the strings? */
list_ele_t *delete;
while (q->head) {
delete = q->head;
q->head = q->head->next;
free(delete->value);
free(delete);
// q->head = q->head->next;
}
/* Free queue structure */
free(q);
}
```
* **q_insert_head**
```cpp
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
/* What should you do if the q is NULL? */
if (q == NULL)
return false;
newh = malloc(sizeof(list_ele_t));
/* Don't forget to allocate space for the string and copy it */
char *add = malloc((strlen(s) + 1) * sizeof(char));
/* What if either call to malloc returns NULL? */
if (newh == NULL || add == NULL) {
free(newh);
free(add);
return false;
}
strcpy(add, s);
newh->value = add;
newh->next = q->head;
q->size = q->size + 1;
if (q->head == NULL) // first insert
q->tail = newh;
q->head = newh;
return true;
}
```
* **q_insert_tail**
```cpp
/* You need to write the complete code for this function */
/* Reme mber: It should operate in O(1) time */
if (q == NULL)
return false;
list_ele_t *newh = malloc(sizeof(list_ele_t));
char *add = malloc((strlen(s) + 1) * sizeof(char));
if (newh == NULL || add == NULL) {
free(newh);
free(add);
return false;
}
strcpy(add, s);
newh->value = add;
newh->next = NULL;
q->size = q->size + 1;
if (q->tail == NULL) { // first insert
q->head = newh;
q->tail = newh;
} else {
q->tail->next = newh;
q->tail = newh;
}
return true;
```
* **q_remove_head**
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
if (q == NULL || q->size == 0)
return false;
if (sp) {
strncpy(sp, q->head->value, bufsize - 1);
sp[bufsize - 1] = '\0';
// strcat(sp,'\0');
}
list_ele_t *replace;
replace = q->head;
q->head = q->head->next;
free(replace->value);
free(replace);
q->size = q->size - 1;
return true;
}
```
* **q_size**
```c=
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 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;
q->head->next = NULL;
list_ele_t *temp;
q->tail = q->head;
while (cur != NULL) {
temp = cur->next;
cur->next = q->head;
q->head = cur;
cur = temp;
}
```
---
## 自動評分運作
* 由於是執行`make test`指令來跑評分系統,所以進入 Makefile 檔中的`test`部分:
```
test: qtest scripts/driver.py
scripts/driver.py
```
可以得知進行自動評分,需要`scripts/driver.py`來執行`qtest`。
* 往`scripts/driver.py`看:
```python=
traceDirectory = "./traces"
qtest = "./qtest"
command = qtest
verbLevel = 0
autograde = False
useValgrind = False
```
```python=
def run(name, args):
prog = ""
tid = 0
vlevel = 1
levelFixed = False
autograde = False
useValgrind = False
optlist, args = getopt.getopt(args, 'hp:t:v:A', ['valgrind'])
for (opt, val) in optlist:
if opt == '-h':
usage(name)
elif opt == '-p':
prog = val
elif opt == '-t':
tid = int(val)
elif opt == '-v':
vlevel = int(val)
levelFixed = True
elif opt == '-A':
autograde = True
elif opt == '--valgrind':
useValgrind = True
else:
print("Unrecognized option '%s'" % opt)
usage(name)
if not levelFixed and autograde:
vlevel = 0
t = Tracer(qtest = prog, verbLevel = vlevel, autograde = autograde, useValgrind = useValgrind)
t.run(tid)
```
在`optlist, args = getopt.getopt(args, 'hp:t:v:A', ['valgrind'])`中:
- `getopt.getopt` 回傳的是兩個陣列: optlist 和 args : optlist是分析出的格式,而 args 為不屬於格式訊息的剩下參數。
- `'hp:t:v:A'` 此處為短選項。**沒有" : "的短選項**就像一個開關,若有在 optlist 中有回傳此格式即觸發;而**有" : "的短選項**表示後面帶一個參數 arg。
- `['valgrind']` 此處為長選項。**沒有" = "的長選項**就像一個開關,若有在 optlist 中有回傳此格式即觸發;而**有" = "的長選項**表示後面帶一個參數 arg。
在`if`條件式中有各項功能。
```python
if opt == '-h':
usage(name)
elif opt == '-p':
prog = val
elif opt == '-t':
tid = int(val)
elif opt == '-v':
vlevel = int(val)
levelFixed = True
elif opt == '-A':
autograde = True
elif opt == '--valgrind':
useValgrind = True
else:
print("Unrecognized option '%s'" % opt)
usage(name)
```
在函式`usage(name)`中有個短選項的各項功能,而`['valgrind']`這個長選項是在決定`useValgrind`功能要不要開啟:
```python
def usage(name):
print("Usage: %s [-h] [-p PROG] [-t TID] [-v VLEVEL] [--valgrind]" % name)
print(" -h Print this message")
print(" -p PROG Program to test")
print(" -t TID Trace ID to test")
print(" -v VLEVEL Set verbosity level (0-3)")
sys.exit(0)
```
最後在呼叫`Tracer`這個 class,來做自動評分:
```python
t = Tracer(qtest = prog, verbLevel = vlevel, autograde = autograde, useValgrind = useValgrind)
t.run(tid)
```
* Reference
-[sys.argv和getopt.getopt()的用法](https://www.cnblogs.com/zz22--/p/9336569.html)
-[Makefile語法和示範](https://hackmd.io/@sysprog/SySTMXPvl?type=view)
---
## qtest 的 signal handler
* [C99規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)
* 7.14中提到 signal 是 conditions that may be reported during program execution.
> 7.14.1 Synopsis
> ```cpp
> #include <signal.h>
> void (*signal(int sig, void (*func)(int)))(int);
> ```
> 7.14.2 Description
The **signal** function chooses one of three ways in which receipt of the signal number **sig** is to be subsequently handled. If the value of func is **SIG_DFL**, default handling for that signal will occur. If the value of func is **SIG_IGN**, the signal will be ignored. Otherwise, **func** shall point to a function to be called when that signal occurs. ==An invocation of such a function because of a signal, or (recursively) of any further functions called by that invocation (other than functions in the standard library), is called a signal handler.==
```cpp
/* Signal handlers */
void sigsegvhandler(int sig)
{
trigger_exception(
"Segmentation fault occurred. You dereferenced a NULL or invalid "
"pointer");
}
void sigalrmhandler(int sig)
{
trigger_exception(
"Time limit exceeded. Either you are in an infinite loop, or your "
"code is too inefficient");
}
static void queue_init()
{
fail_count = 0;
q = NULL;
signal(SIGSEGV, sigsegvhandler);
signal(SIGALRM, sigalrmhandler);
}
```
* **SIGALRM** : Time limit exceeded
在`harness.h`中有`exception_setup` . `exception_cancel` . `trigger_exception`三個函式:
```c
bool exception_setup(bool limit_time)
{
if (sigsetjmp(env, 1)) {
/* Got here from longjmp */
jmp_ready = false;
if (time_limited) {
alarm(0);
time_limited = false;
}
if (error_message) {
report_event(MSG_ERROR, error_message);
}
error_message = "";
return false;
} else {
/* Got here from initial call */
jmp_ready = true;
if (limit_time) {
alarm(time_limit);
time_limited = true;
}
return true;
}
}
void exception_cancel()
{
if (time_limited) {
alarm(0);
time_limited = false;
}
jmp_ready = false;
error_message = "";
}
void trigger_exception(char *msg)
{
error_occurred = true;
error_message = msg;
if (jmp_ready)
siglongjmp(env, 1);
else
exit(1);
}
```
* [sigsetjmp(sigjmp_buf env, int savemask)](http://www.qnx.com/developers/docs/qnxcar2/index.jsp?topic=%2Fcom.qnx.doc.neutrino.lib_ref%2Ftopic%2Fs%2Fsigsetjmp.html) 會保存目前 stack ,然後將目前的地址記錄起來,而在其他函數用 [siglongjmp()](http://www.qnx.com/developers/docs/qnxcar2/index.jsp?topic=%2Fcom.qnx.doc.neutrino.lib_ref%2Ftopic%2Fs%2Fsigsetjmp.html) 時便會直接跳到所記錄的位址,還原 stack ,再繼續執行其他函式。
env : A buffer where the function can save the calling environment.
savemask : Nonzero if you want to save the process's current signal mask, otherwise 0.
* [alarm(unsigned int seconds)](http://man7.org/linux/man-pages/man2/alarm.2.html) 當呼叫了alarm(n)後,等待n秒後,就會觸發一次的SIGALRM的signal,如果 alarm 的參數 seconds 為0,則之前設置的 Timer 會被取消,並將剩下的時間返回。
所以`bool exception_setup(bool limit_time)` 是用來判別程序執行時是否超過指定時間( time_limit = 1)。
- [ ]**若超過時間**:
`jmp_ready == true` → 呼叫`trigger_exception`儲存錯誤訊息( error_message )且將 error_occurred 設成 true → 由 `siglongjmp` 返回 `exception_setup` 進入 if 迴圈 重新計時且顯示錯誤訊息,最後回傳false。
- [ ]**若沒有超過時間**:
`time_limited == true`→ 呼叫`exception_cancel`重新計時且將`jmp_ready`設回 false , error_message 也設為空白字元使其不顯示