# 2018q3 Homework2 (lab0)
contributed by < [`butastur-rtos`](https://github.com/butastur-rtos) >
* [Overview](http://www.cs.cmu.edu/afs/cs/academic/class/15513-m18/www/lectures/01-overview.pdf)
* [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
* Studying man page
* linked list
* queue
* implements each function
* 探討一下不同的實作品味
* 解釋自動評分系統運作的原理
* 研究自動測試機制
## [Overview](http://www.cs.cmu.edu/afs/cs/academic/class/15513-m18/www/lectures/01-overview.pdf)
* page 22 ~ page 33 在說明作弊這件事, 整份文件只有59頁, 其中10頁在說明作弊的認定和後果, 最毛骨悚然的是第 26 頁的這句話, ==Loss of respect by you, the instructors and your colleagues== [ [ `Overview` ] ](http://www.cs.cmu.edu/afs/cs/academic/class/15513-m18/www/lectures/01-overview.pdf)
* 學習最重要的就是==誠實的面對自己==, 所以這個 homework 會依照==和 CMU 學生同樣標準的Academic Integrity==來撰寫
* 節錄自第27頁
:::info
### This is Cheating:
- Searching the internet with the phrase 15-213, 15213, 213, 18213, malloclab, etc.
- That’s right, just entering it in a search engine
:::
* 不過第 27 頁同樣也有說明什麼是被鼓勵的
:::info
### This is OK (and encouraged):
- Googling a man page for fputs
- Asking a friend for help with gdb
- Asking a TA or course instructor for help, showing them your code, …
- Looking in the textbook for a code example
- Talking about a (high-level) approach to the lab with a classmate
:::
## [C Programming Lab](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf)
* [Union](https://en.wikibooks.org/wiki/C_Programming/Advanced_data_types)
union 的 size 取決於 member 裡 size 最大的 member
```clike
#include <stdio.h>
int main(){
union {
int i;
char c;
} u;
printf("sizeof(char): %ld\r\n", sizeof(char));
printf("sizeof(int): %ld\r\n", sizeof(int));
printf("sizeof(u): %ld\r\n", sizeof(u));
return 0;
}
```
```shell
$ gcc -o union union.c
$ ./union
sizeof(char): 1
sizeof(int): 4
sizeof(u): 4
```
## Study man page
* man malloc
* man free
## linked list
* macro
* union
* list.h
## queue
## implements each function
* $O(1)$
* q_insert_tail
* q_size
* q_remove_head
* q_insert_head
* q_reverse
$O(1)$ 是一個 notation,表示執行的時間是一個常數,不會因為輸入的資料大小而執行的時間有所不同。
因為要在 $O(1)$ 的時間內執行完成 q_insert_tail, 所以在 list 裡一個一個往下找 tail 的作法是不可能的。 [page 4](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf) 的圖有一個 head 的additional fields, 應該可以在每一次的 q_insert_tail 紀錄 list 裡最後一個 node 的 address, 所以初步的想法是<b>從 head 的 additional fields</b> 開始著手
[ [queue.h] ](https://github.com/butastur-rtos/lab0-c/blob/master/queue.h#L27)
果然,註解有這麼一段話... add more fields...
```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
*/
} queue_t;
```
>中英文字間記得空白
>[name=課程助教][color=red]
* 開發紀錄
先設定 github
```shell
$ ssh-keygen -t rsa -C "butastur@foxmail.com"
```
commit 試一下
```shell
$ git commit
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
modified: queue.h
no changes added to commit
```
So, use `git add` like:
```shell
$ git add queue.h
```
* commit 之前, study 一下 [如何寫好 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 試試
```shell=
$ git commit -m "add fields to point to the tail"
add fields to point to the tail [line 1]
- Capitalize the subject line
Proceed with commit? [e/n/?] e
add fields to point to the tail [line 1]
- Capitalize the subject line
Proceed with commit? [e/n/?] y
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] ?
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] e
[master 23f7113] Add fields to point to the tail
```
第4行輸入 e,可以編輯 message
第7行輸入 y,因為 y 不是有效選項, 所以出現 help
第17行輸入 e, 再次編輯 message, 因為 message 有限制標題開頭要大寫
:::danger
commit 還是失敗, 所以依照 git 的提示再試一下
git config --global --edit
git commit --amend --reset-author
:::
```shell
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:
git config --global --edit
After doing this, you may fix the identity used for this commit with:
git commit --amend --reset-author
1 file changed, 1 insertion(+)
```
commit 之前修改一下 message
``` shell
$ git commit
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
```
看起來, 應該要執行 `git push` 才對
```shell
$ git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 324 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
7251dad..b119f00 master -> master
```
:::info
將 git repository 的 https 改為 ssh,並且設定好 ssh-key,之後 git push 就不需要輸入帳號密碼
參照 [更換遠端伺服器倉庫網址](https://gist.github.com/fokayx/255b228ded2bca1c4f60)
:notes: jserv
:::
#### 接下來實作 function q_size
`int q_size(queue_t *q)` 由於要在 $O(1)$ 的常數時間內執行完成, 所以也不可能每次都遍歷整個 list 來取得 queue 的大小,
[`queue.h`](https://github.com/butastur-rtos/lab0-c/blob/master/queue.h#L30)的註解有寫You will need to add more fields to this structure to efficiently implement q_size and q_insert_tail
所以, 增加 <b>int size</b> 到typedef struct queue_t
```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;
int size;
} queue_t;
```
接下來修改 q_size 的傳回值, 改成傳回q->size
```shell
$ git commit -m "Change q_size return value to q->size"
--- .merge_file_Iu0bic 2018-09-21 13:33:12.940675156 -0500
+++ /tmp/.merge_file_Iu0bic.rV006h 2018-09-21 13:33:12.953675156 -0500
@@ -70,7 +70,7 @@ 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 *newt; // newt means new tail
+ list_ele_t *newt; // newt means new tail
newt = malloc(sizeof(list_ele_t));
q->tail = newt;
return true;
[!] queue.c does not follow the consistent coding style.
Make sure you indent as the following:
clang-format -i queue.c
```
:::danger
格式不對, 所以 commit 失敗
:::
依 git 指示執行 `clang-format` 後再重新 `git add`, 然後再 `git commit`, `git push`
```shell
$ clang-format -i queue.c
$ git add queue.c
$ git commit -m "Change q_size return value to q->size"
[master d923e13] Change q_size return value to q->size
1 file changed, 7 insertions(+), 2 deletions(-)
git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 419 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
8c93ea5..d923e13 master -> master
```
#### 接下來實作 q_remove_head
Remove one element, so the size should decrease, 第6行把size減一
```clike
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* You need to fix up this code. */
q->head = q->head->next;
/* remove one element, so the size should decrease */
q->size--;
return true;
}
```
```shell
$ git commit -m "Remove one elem, q_remove_head decrease q->size"
[master 67b3062] Remove one elem, q_remove_head decrease q->size
1 file changed, 2 insertions(+)
$ git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 386 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
d923e13..67b3062 master -> master
```
:::info
應該先用 `qtest` 測試過
:notes: jserv
:::
:::info
避免用 `git commit -m`,設定好 $EDITOR 環境變數,用你偏好的編輯器寫好 git commit messages
:notes: jserv
:::
### q_insert_head
q_insert_head的parameter q如果是NULL, 要傳回false
```clike
bool q_insert_head(queue_t *q, char *s)
{
if (q == NULL) {
return false;
}
...
}
```
```shell
$ git add queue.c
$ git commit -m "In q_insert_head, if q == NULL, return false"
[master 3a7d956] In q_insert_head, if q == NULL, return false
1 file changed, 4 insertions(+)
$git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 362 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
67b3062..3a7d956 master -> master
```
### q_new
initialize a new queue, if malloc return NULL q_new return NULL
```clike
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* What if malloc returned NULL? if malloc return NULL q_new return NULL */
if (q != NULL) {
q->head = NULL;
/* size initialize to zero */
q->size = 0;
/* tail initialize to NULL */
q->tail = NULL;
}
return q;
}
```
```shell
To https://github.com/butastur-rtos/lab0-c.git
3a7d956..c96f697 master -> master
```
q_remove_head須要Return false if queue is NULL or empty
```clike
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if(q == NULL || q->size == 0)
return false;
...
}
```
```shell
$ git add queue.c
$ git commit -m "For remove_head,return false if queue [NULL|empty]"
[master c7484a8] For remove_head,return false if queue [NULL|empty]
1 file changed, 10 insertions(+), 4 deletions(-)
$ git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 456 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
c96f697..c7484a8 master -> master
```
### q_free
:::warning
是不是 prev->value 也要 free?
:::
> 這點我也很好奇,但我在釋放 `prev` 的記憶體空間前先釋放 `prev->value` 的記憶體空間卻出現以下錯誤訊息:
> ```shell
> ERROR: Attempt to free unallocated or corrupted block.
> ```
> [name=Lawrence][time=Sun, Sep 23, 2018 3:56 PM]
>> 我也是碰到一樣的 ERROR, 因為 prev->value 是透過 strdup 取得 a pointer to a new string, 原本好奇會不會是因為這樣所以不能 free, 於是查了一下 man, 查到這一段
> The strdup() function returns a pointer to a new string which is a
duplicate of the string s. Memory for the new string is obtained with
malloc(3), and can be freed with free(3).
所以我還需要再研究一下要不要 free(prev->value)
> [name=butastur-rtos][time=Sun, Sep 23, 2018 5:31 PM]
> > 是 strdup 惹的禍,因為 harness.h 有段 macro 會把 free 換成一個自己開發的函式,導致你用不到真正的 free ,變成在用 malloc 與 test_free ,因此直接用 malloc 讓 preprocessor 把它也轉成 test_malloc 就好了。
> > [name=pjchiou] [time=2018 Sep 27 Thu 05:58]
>>> 我試一下用 malloc
>>> [name=butastur-rtos] [time=2018 Sep 27 Thu 08:43]
```clike
void q_free(queue_t *q)
{
/* How about freeing the list elements and the strings? */
/* Free queue structure */
list_ele_t *pos;
for (pos = q->head; pos != NULL;) {
list_ele_t *prev = pos;
pos = pos->next;
free(prev);
}
free(q);
}
```
```shell
$ git add queue.c
$ git commit -m "Implement q_free, free all storage used by queue"
[master 0e7a011] Implement q_free, free all storage used by queue
1 file changed, 6 insertions(+)
$ git push
Username for 'https://github.com': butastur-rtos
Password for 'https://butastur-rtos@github.com':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 411 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/butastur-rtos/lab0-c.git
c7484a8..0e7a011 master -> master
```
### q_size
修改 q_size 的傳回值
```clike
int q_size(queue_t *q)
{
return q != NULL ? q->size : 0;
}
```
```shell
$ git commit -m "Implement q_size, return 0 if q is NULL or empty"
```
```shell
$ git commit -m "Change insert_head, insert_tail logical"
```
```shell
$ git commit -m "Use free to free storage for q_free"
```
```shell
$ git commit -m "When free a NULL queue, just do nothing"
```
### q_reverse
[ [ source ] ](https://github.com/butastur-rtos/lab0-c/blob/master/queue.h)
```clike
/* Linked list element (You shouldn't need to change this) */
typedef struct ELE {
/* Pointer to array holding string.
This array needs to be explicitly allocated and freed */
char *value;
struct ELE *next;
} list_ele_t;
...
/*
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);
```
實作 q_reverse 要注意兩點, 第一, 不可以增加 list_ele_t的欄位來作雙方鏈結, 第二, 不可以對 list 的 element 作allocate or free, 只可以 rearrange the existing ones.
初期的想法是分兩個步驟:
第一, <b>從 tail 開始, 每一個 list_ele_t 的 next 都指向前一個 list_ele_t</b>
第二, <b>等每一個 list_ele_t 的 next 都指向前一個之後, swap q->head 和 q->tail</b>
以上兩個步驟好像太普通,我想試試以下這個方法, 節錄自 [include/linux/list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h#L504)
[ [`source`] ](https://github.com/torvalds/linux/blob/master/include/linux/list.h#L504)
```clike
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_last_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_prev_entry(pos, member))
```
:::danger
不過再仔細看一下, 這個方法不行, 因為這是用在 doubly linked list
:::
如果用以下的順序, 寫出來會更糟或是一般般呢? 先畫成 Graph 思考一下
```graphviz
digraph G {
rankdir=LR;
subgraph cluster_11 {
label=Step11;
color=black;
node [style=filled,color=lightgrey,shape=record];
k2 -> k1; k3 -> k2; k1->k0;
}
subgraph cluster_10 {
label=Step10;
color=black;
node [style=filled,color=lightgrey,shape=record];
j2 -> j1; j3 -> j2; j1->j0;
}
subgraph cluster_9 {
label=Step9;
color=black;
node [style=filled,color=lightgrey,shape=record];
i0 -> i1; i2 -> i1; i3 -> i2; i1->i0;
}
subgraph cluster_8 {
label=Step8;
color=black;
node [style=filled,color=lightgrey,shape=record];
h0 -> h1; h2 -> h1; h3 -> h2;
}
subgraph cluster_7 {
label=Step7;
color=black;
node [style=filled,color=lightgrey,shape=record];
g0 -> g1 -> g2 -> g1; g3 -> g2;
}
subgraph cluster_6 {
label=Step6;
color=black;
node [style=filled,color=lightgrey,shape=record];
f0 -> f1 -> f2; f3 -> f2;
}
subgraph cluster_5 {
label=Step5;
color=black;
node [style=filled,color=lightgrey,shape=record];
e0 -> e1 -> e2; e3 -> e2;
}
subgraph cluster_4 {
label=Step4;
color=black;
node [style=filled,color=lightgrey,shape=record];
d0 -> d1 -> d2 -> d3 -> d2;
}
subgraph cluster_3 {
label=Step3;
color=black;
node [style=filled,color=lightgrey,shape=record];
c0 -> c1 -> c2 -> c3;
}
subgraph cluster_1 {
label=Step1;
color=black;
node [style=filled,color=lightgrey,shape=record];
a0 -> a1 -> a2 -> a3;
}
prev_a -> a0;
head_a -> a0;
tail_a -> a3
a3 -> NULL_a;
head_c -> c0;
prev_c -> c2;
tail_c -> c3;
c3 -> NULL_c;
head_d -> d0;
prev_d -> d2;
tail_d -> d3;
head_e -> e0;
prev_e -> e0;
tail_e -> e3;
e2 -> NULL_e;
head_f -> f0;
prev_f -> f1;
tail_f -> f3;
f2 -> NULL_f;
prev_g -> g1;
head_g -> g0;
tail_g -> g3;
head_h -> h0;
prev_h -> h0;
tail_h -> h3;
h1 -> NULL_h;
head_i -> i0;
prev_i -> i0;
tail_i -> i3;
head_j -> j0;
prev_j -> j0;
tail_j -> j3;
j0 -> NULL_j;
head_k -> k3;
prev_k -> k3;
tail_k -> k0;
k0 -> NULL_k;
tail_a [shape=plaintext,label=tail];
tail_c [shape=plaintext,label=tail];
tail_d [shape=plaintext,label=tail];
tail_e [shape=plaintext,label=tail];
tail_f [shape=plaintext,label=tail];
tail_g [shape=plaintext,label=tail];
tail_h [shape=plaintext,label=tail];
tail_i [shape=plaintext,label=tail];
tail_j [shape=plaintext,label=tail];
tail_k [shape=plaintext,label=tail];
head_a [shape=plaintext,label=head];
head_c [shape=plaintext,label=head];
head_d [shape=plaintext,label=head];
head_e [shape=plaintext,label=head];
head_f [shape=plaintext,label=head];
head_g [shape=plaintext,label=head];
head_h [shape=plaintext,label=head];
head_i [shape=plaintext,label=head];
head_j [shape=plaintext,label=head];
head_k [shape=plaintext,label=head];
prev_a [shape=plaintext,label=prev];
prev_c [shape=plaintext,label=prev];
prev_d [shape=plaintext,label=prev];
prev_e [shape=plaintext,label=prev];
prev_f [shape=plaintext,label=prev];
prev_g [shape=plaintext,label=prev];
prev_h [shape=plaintext,label=prev];
prev_i [shape=plaintext,label=prev];
prev_j [shape=plaintext,label=prev];
prev_k [shape=plaintext,label=prev];
NULL_a [shape=plaintext,label=NULL];
NULL_c [shape=plaintext,label=NULL];
NULL_e [shape=plaintext,label=NULL];
NULL_f [shape=plaintext,label=NULL];
NULL_h [shape=plaintext,label=NULL];
NULL_j [shape=plaintext,label=NULL];
NULL_k [shape=plaintext,label=NULL];
c0[label=a0];
c1[label=a1];
c2[label=a2];
c3[label=a3];
d0[label=a0];
d1[label=a1];
d2[label=a2];
d3[label=a3];
e0[label=a0];
e1[label=a1];
e2[label=a2];
e3[label=a3];
f0[label=a0];
f1[label=a1];
f2[label=a2];
f3[label=a3];
g0[label=a0];
g1[label=a1];
g2[label=a2];
g3[label=a3];
h0[label=a0];
h1[label=a1];
h2[label=a2];
h3[label=a3];
i0[label=a0];
i1[label=a1];
i2[label=a2];
i3[label=a3];
j0[label=a0];
j1[label=a1];
j2[label=a2];
j3[label=a3];
k0[label=a0];
k1[label=a1];
k2[label=a2];
k3[label=a3];
}
```
==如果用這個方法, 寫出來會更糟或是一般般呢?== 繼續實驗...
```clike
void q_reverse(queue_t *q)
{
if (q != NULL && q->size && q->tail != NULL) {
list_ele_t *prev;
while (q->head != NULL
&& q->head->next != NULL
&& q->head->next->next != q->head) {
prev = q->head;
while (prev->next != NULL && prev->next->next != NULL)
prev = prev->next;
if (prev->next != NULL && prev->next->next == NULL) {
prev->next->next = prev;
prev->next = NULL;
}
}
q->head->next = NULL;
q->head = q->tail;
q->tail = prev;
prev = q->head;
}
}
```
`qtest` 測試的結果是
```shell
$ ./qtest
cmd>new
cmd>new
q = []
cmd>ih a0
cmd>ih a0
q = [a0]
cmd>ih a1
cmd>ih a1
q = [a1 a0]
cmd>ih a2
cmd>ih a2
q = [a2 a1 a0]
cmd>ih a3
cmd>ih a3
q = [a3 a2 a1 a0]
cmd>reverse
cmd>reverse
q = [a0 a1 a2 a3]
```
`make test` 的結果是
```shell
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, and size
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head reverse, and size
--- trace-05-ops 6/6
```
:::danger
但是這樣寫的效能太差, 所以 Time limit exceeded, ~~這樣寫會導致效能慢也是可以預期的, 畢竟每次找prev都是從頭開始找~~
:::
```shell
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
--- trace-13-perf 0/7
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 7/7
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, and reverse
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
ERROR: Time limit exceeded. Either you are in an infinite loop, or your code is too inefficient
--- trace-15-perf 0/7
--- TOTAL 86/100
```
:::danger
由於效能太差, 所以要思考一下有什麼改善的方法?
:::
> 或許可以不只用`prev`,多用幾個 pointers 記錄
> [name=Lawrence][time=Sun, Sep 23, 2018 3:50 PM]
>> 多用兩個 pointer 果然有效
>> [name=butastur-rtos][time=Thu, Sep 27, 2018 8:46 PM]
<br>
<b>如果用以下的流程?</b>
```graphviz
digraph G {
rankdir=LR;
subgraph cluster_10 {
label=Step10;
color=black;
node [style=filled,color=lightgrey,shape=record];
a1_10 -> a0_10; a2_10 -> a1_10; a3_10;
}
subgraph cluster_9 {
label=Step9;
color=black;
node [style=filled,color=lightgrey,shape=record];
a1_9 -> a0_9; a2_9 -> a1_9; a3_9;
}
subgraph cluster_8 {
label=Step8;
color=black;
node [style=filled,color=lightgrey,shape=record];
a0_8 -> a1_8 -> a0_8; a2_8 -> a1_8; a3_8;
}
subgraph cluster_7 {
label=Step7;
color=black;
node [style=filled,color=lightgrey,shape=record];
g0_7 -> g1_7 -> g0_7; g2_7 -> g1_7; g3_7;
}
subgraph cluster_6 {
label=Step6;
color=black;
node [style=filled,color=lightgrey,shape=record];
f0_6 -> f1_6 -> f0_6; f2_6 -> f3_6;
}
subgraph cluster_4 {
label=Step4;
color=black;
node [style=filled,color=lightgrey,shape=record];
a0_4 -> a1_4 -> a0_4; a2_4 -> a3_4;
}
subgraph cluster_3 {
label=Step3;
color=black;
node [style=filled,color=lightgrey,shape=record];
d0 -> d1 -> d2 -> d3;
}
subgraph cluster_2 {
label=Step2;
color=black;
node [style=filled,color=lightgrey,shape=record];
c0 -> c1 -> c2 -> c3;
}
subgraph cluster_1 {
label=Step1;
color=black;
node [style=filled,color=lightgrey,shape=record];
a0 -> a1 -> a2 -> a3;
}
prev_a -> a0;
head_a -> a0;
tail_a -> a3
a3 -> NULL_a;
head_c -> c0;
prev_c -> c0;
tail_c -> c1;
c3 -> NULL_c;
head_3 -> d0;
prev_3 -> d0;
tail_3 -> d2;
d3 -> NULL_d;
head_4 -> a0_4;
prev_4 -> a0_4;
tail_4 -> a2_4;
a3_4 -> NULL_4;
head_f -> f0_6;
prev_f -> f1_6;
tail_f -> f2_6;
f3_6 -> NULL_f;
prev_g -> g2_7;
head_g -> g0_7;
tail_g -> g3_7;
g3_7 -> NULL_g;
prev_8 -> a2_8;
head_8 -> a0_8;
tail_8 -> a3_8;
a3_8 -> a2_8;
prev_9 -> a2_9;
head_9 -> a0_9;
tail_9 -> a3_9;
a3_9 -> a2_9;
a0_9 -> NULL_9;
prev_10 -> a2_10;
tail_10 -> a0_10;
head_10 -> a3_10;
a3_10 -> a2_10;
a0_10 -> NULL_10;
tail_a [shape=plaintext,label=tail];
tail_c [shape=plaintext,label=tail];
tail_3 [shape=plaintext,label=tail];
tail_4 [shape=plaintext,label=tail];
tail_f [shape=plaintext,label=tail];
tail_g [shape=plaintext,label=tail];
tail_8 [shape=plaintext,label=tail];
tail_9 [shape=plaintext,label=tail];
tail_10 [shape=plaintext,label=tail];
head_a [shape=plaintext,label=head];
head_c [shape=plaintext,label=head];
head_3 [shape=plaintext,label=head];
head_4 [shape=plaintext,label=head];
head_f [shape=plaintext,label=head];
head_g [shape=plaintext,label=head];
head_8 [shape=plaintext,label=head];
head_9 [shape=plaintext,label=head];
head_10 [shape=plaintext,label=head];
prev_a [shape=plaintext,label=prev];
prev_c [shape=plaintext,label=prev];
prev_3 [shape=plaintext,label=prev];
prev_4 [shape=plaintext,label=prev];
prev_f [shape=plaintext,label=prev];
prev_g [shape=plaintext,label=prev];
prev_8 [shape=plaintext,label=prev];
prev_9 [shape=plaintext,label=prev];
prev_10 [shape=plaintext,label=prev];
NULL_a [shape=plaintext,label=NULL];
NULL_c [shape=plaintext,label=NULL];
NULL_d [shape=plaintext,label=NULL];
NULL_4 [shape=plaintext,label=NULL];
NULL_f [shape=plaintext,label=NULL];
NULL_g [shape=plaintext,label=NULL];
NULL_9 [shape=plaintext,label=NULL];
NULL_10 [shape=plaintext,label=NULL];
c0[label=a0];
c1[label=a1];
c2[label=a2];
c3[label=a3];
d0[label=a0];
d1[label=a1];
d2[label=a2];
d3[label=a3];
a0_4[label=a0];
a1_4[label=a1];
a2_4[label=a2];
a3_4[label=a3];
f0_6[label=a0];
f1_6[label=a1];
f2_6[label=a2];
f3_6[label=a3];
g0_7[label=a0];
g1_7[label=a1];
g2_7[label=a2];
g3_7[label=a3];
a0_8[label=a0];
a1_8[label=a1];
a2_8[label=a2];
a3_8[label=a3];
a0_9[label=a0];
a1_9[label=a1];
a2_9[label=a2];
a3_9[label=a3];
a0_10[label=a0];
a1_10[label=a1];
a2_10[label=a2];
a3_10[label=a3];
}
```
:::info
上面這個流程只用一個 `prev` 並沒有辦法完成, 所以看起來加一個 `prev` 不夠, 如 Lawrence 建議再加2個 pointers 試試看, 一個 `current`, 代表 `prev` 的下一個, 一個 `next`, 指向 `current` 的下一個
:::
:::info
先將目前進度 commit
:::
```shell
$ clang-format -i queue.c
$ git add queue.c
$ git commit --amend
[master 85ee74b] Implement q_reverse
Date: Fri Sep 21 21:07:13 2018 -0500
1 file changed, 27 insertions(+), 2 deletions(-)
$ git push origin master --force
Enter passphrase for key '/root/.ssh/id_rsa':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 986 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:butastur-rtos/lab0-c.git
+ cd8dc61...85ee74b master -> master (forced update)
```
:::info
使用3個 pointers 來紀錄 element of list 後效能有提升
:::
```clike
void q_reverse(queue_t *q)
{
if (q != NULL && q->size) {
list_ele_t *prev, *current, *next;
prev = q->head;
current = q->head->next;
next = q->head->next->next;
prev->next = NULL;
current->next = prev;
while (next != NULL) {
prev = current;
current = next;
next = next->next;
current->next = prev;
}
q->tail = q->head;
q->head = current;
}
}
```
```shell
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 7/7
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 7/7
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, and reverse
--- trace-15-perf 7/7
--- TOTAL 93/100
```
```shell
To https://github.com/butastur-rtos/lab0-c.git
+ 85ee74b...cda8046 master -> master (forced update)
```
:::info
#### 設定 git 使用 SSH Key, 免得每次 commit 都要敲 username, password
:::
設定SSH, 然後使用 vim 編輯 message 後再 commit
```shell
$ git commit --amend
[master cd8dc61] Meet remove_head requirement
Date: Fri Sep 21 21:07:13 2018 -0500
1 file changed, 9 insertions(+), 2 deletions(-)
$ git push origin master --force
Enter passphrase for key '/root/.ssh/id_rsa':
ERROR: The key you are authenticating with has been marked as read only.
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
root@ut:~/temp/ssh/lab0-c#
```
所以 github 上的 authenticating 要再改一下
```shell
$ git push origin master --force
Enter passphrase for key '/root/.ssh/id_rsa':
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 603 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:butastur-rtos/lab0-c.git
+ 81d7a17...cd8dc61 master -> master (forced update)
```
:::info
q_reverse 在不使用 doubly linked list 的前提下, 試過以現有的q->head, q->tail來實作, 但是效能太低, 試過只加一個 pointer prev 來實作, 但是程式複雜度提高, 最後加了3個pointers prev, current, next 這三個pointer, 在對每一個 element 作 reverse 的時候, 來紀錄 element 的前後兩個 element 的address, 最後解決了這個效能的問題, 這個解法是在不使用 doubly linked list 的前提下, 網路上常見的寫法
:::
:::danger
以下這個 error, 會使用 `qtest` 來 debug, 並用這個 error 來說明 `qtest`
:::
```shell
+++ TESTING trace trace-06-string:
# Test of truncated strings
ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer
--- trace-06-string 0/7
```
看一下 trace-06-string 作了什麼測試
```shell=
$ cat traces/trace-06-string.cmd
# Test of truncated strings
option fail 0
option malloc 0
new
ih aardvark_bear_dolphin_gerbil_jaguar 5
it meerkat_panda_squirrel_vulture_wolf 5
rh aardvark_bear_dolphin_gerbil_jaguar
reverse
rh meerkat_panda_squirrel_vulture_wolf
option length 30
rh meerkat_panda_squirrel_vulture
reverse
option length 28
rh aardvark_bear_dolphin_gerbil
option length 21
rh aardvark_bear_dolphin
reverse
option length 22
rh meerkat_panda_squirrel
option length 7
rh meerkat
reverse
option length 8
rh aardvark
option length 100
rh aardvark_bear_dolphin_gerbil_jaguar
reverse
rh meerkat_panda_squirrel_vulture_wolf
free
```
```shell
$ ./qtest
...
cmd>rh aardvark_bear_dolphin_gerbil_jaguar
Removed aardvark_bear_dolphin_gerbil_jaguar from queue
q = [meerkat_panda_squirrel_vulture_wolf]
cmd>reverse
ERROR: Segmentation fault occurred. You dereferenced a NULL or invalid pointer
q = [meerkat_panda_squirrel_vulture_wolf ... ]
```
一步一步的執行, 直到==第28行==的 reverse 產生一個 Segmentation fault, 所以從 q_reverse 來檢查一下
在第 4 行加上 NULL 的判斷
```clike=
void q_reverse(queue_t *q)
{
if (q != NULL && q->size) {
if(q->head->next == NULL){
return;
}
...
}
...
}
```
測試結果
```shell=
$ make test
...
--- trace-15-perf 7/7
--- TOTAL 100/100
```
:::danger
目前實作碼的品味不夠, 在9/27(四)之前應該要再把目前的實作碼改的比較有品味
> [name=butastur-rtos]
:::
### 使用工具來分析 q_reverse 的效能
* $O(n)$
* valgrind
用 `valgrind` 來檢查一下 [`qtest`](https://github.com/butastur-rtos/lab0-c/blob/master/queue.c) 的執行結果有沒有 memory leak
```shell
$ valgrind ./qtest
cmd>new
cmd>new
q = []
cmd>ih str 1000
cmd>ih str 1000
q = [str str str str str str str str str str str str str str str str str str str str str str str str str str str str str str ... ]
cmd>quit
cmd>quit
Freeing queue
==18771==
==18771== HEAP SUMMARY:
==18771== in use at exit: 4,000 bytes in 1,000 blocks
==18771== total heap usage: 2,041 allocs, 1,041 frees, 70,190 bytes allocated
==18771==
==18771== LEAK SUMMARY:
==18771== definitely lost: 4,000 bytes in 1,000 blocks
==18771== indirectly lost: 0 bytes in 0 blocks
==18771== possibly lost: 0 bytes in 0 blocks
==18771== still reachable: 0 bytes in 0 blocks
==18771== suppressed: 0 bytes in 0 blocks
==18771== Rerun with --leak-check=full to see details of leaked memory
==18771==
==18771== For counts of detected and suppressed errors, rerun with: -v
==18771== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```
對 `valgrind` 多一些嘗試, 試一下 `valgrind --leak-check=full`
```shell
$ valgrind --leak-check=full ./qtest
==18783== Memcheck, a memory error detector
==18783== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==18783== Using Valgrind-3.12.0.SVN and LibVEX; rerun with -h for copyright info
==18783== Command: ./qtest
==18783==
cmd>new
cmd>new
q = []
cmd>ih str 1000
cmd>ih str 1000
q = [str str str str str str str str str str str str str str str str str str str str str str str str str str str str str str ... ]
cmd>quit
cmd>quit
Freeing queue
==18783==
==18783== HEAP SUMMARY:
==18783== in use at exit: 4,000 bytes in 1,000 blocks
==18783== total heap usage: 2,036 allocs, 1,036 frees, 70,141 bytes allocated
==18783==
==18783== 4,000 bytes in 1,000 blocks are definitely lost in loss record 1 of 1
==18783== at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==18783== by 0x4EB83B9: strdup (strdup.c:42)
==18783== by 0x10D1E0: q_insert_head (queue.c:75)
==18783== by 0x10968C: do_insert_head (qtest.c:166)
==18783== by 0x10BB68: interpret_cmda (console.c:218)
==18783== by 0x10BBFC: interpret_cmd (console.c:239)
==18783== by 0x10CA14: cmd_select (console.c:605)
==18783== by 0x10CB67: run_console (console.c:642)
==18783== by 0x10A77D: main (qtest.c:573)
==18783==
==18783== LEAK SUMMARY:
==18783== definitely lost: 4,000 bytes in 1,000 blocks
==18783== indirectly lost: 0 bytes in 0 blocks
==18783== possibly lost: 0 bytes in 0 blocks
==18783== still reachable: 0 bytes in 0 blocks
==18783== suppressed: 0 bytes in 0 blocks
==18783==
==18783== For counts of detected and suppressed errors, rerun with: -v
==18783== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```
:::info
說明 `qtest` 的行為
> [name=butastur-rtos]
:::
<hr>
### 目前的進度
```shell
$make test
scripts/driver.py
--- Trace Points
+++ TESTING trace trace-01-ops:
# Test of insert_head and remove_head
--- trace-01-ops 6/6
+++ TESTING trace trace-02-ops:
# Test of insert_head, insert_tail, and remove_head
--- trace-02-ops 6/6
+++ TESTING trace trace-03-ops:
# Test of insert_head, insert_tail, reverse, and remove_head
--- trace-03-ops 6/6
+++ TESTING trace trace-04-ops:
# Test of insert_head, insert_tail, and size
--- trace-04-ops 6/6
+++ TESTING trace trace-05-ops:
# Test of insert_head, insert_tail, remove_head reverse, and size
--- trace-05-ops 6/6
+++ TESTING trace trace-06-string:
# Test of truncated strings
--- trace-06-string 7/7
+++ TESTING trace trace-07-robust:
# Test operations on NULL queue
--- trace-07-robust 7/7
+++ TESTING trace trace-08-robust:
# Test operations on empty queue
--- trace-08-robust 7/7
+++ TESTING trace trace-09-robust:
# Test remove_head with NULL argument
--- trace-09-robust 7/7
+++ TESTING trace trace-10-malloc:
# Test of malloc failure on new
--- trace-10-malloc 7/7
+++ TESTING trace trace-11-malloc:
# Test of malloc failure on insert_head
--- trace-11-malloc 7/7
+++ TESTING trace trace-12-malloc:
# Test of malloc failure on insert_tail
--- trace-12-malloc 7/7
+++ TESTING trace trace-13-perf:
# Test performance of insert_tail
--- trace-13-perf 7/7
+++ TESTING trace trace-14-perf:
# Test performance of size
--- trace-14-perf 7/7
+++ TESTING trace trace-15-perf:
# Test performance of insert_tail, size, and reverse
--- trace-15-perf 7/7
--- TOTAL 100/100
```
:::info
100/100 PASS
:::
## 解釋自動評分系統運作的原理
* qtest 如何 detect corruption
* 說明 qtest 的行為
### qtest 如何 detect corruption
* What is Corruption detected?
qtest detect 到 corruption 時會出現以下 訊息, 接著說明 qtest 是如何辦到這件事的
```shell
# Test performance of insert_tail, size, and reverse
ERROR: Attempted to free unallocated or corrupted block. Address = 0x561698e80580
ERROR: Corruption detected in block with address 0x561698e80580 when attempting to free it
*** Error in `./qtest': free(): invalid next size (fast): 0x0000561698e80560 ***
```
qtest 在 free 的時候, 實際上是執行 test_free, 這個 function 會呼叫 find_header, find_footer, 通過判斷 block 的 footer, 如果不是 [<b>0xbeefdead</b>(也就是MAGICFOOTER)](https://github.com/butastur-rtos/lab0-c/blob/master/harness.c#L23), 就會被視為 Corruption detected
接下來解釋 block, footer, 以及說明 test_malloc, find_header, find_footer 彼此之間是如何動作的
[ [`harness.c`] ](https://github.com/butastur-rtos/lab0-c/blob/master/harness.c#L35)
```clike
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;
...
block_ele_t *new_block =
malloc(size + sizeof(block_ele_t) + sizeof(size_t));
```
大致上, test_malloc 的時候除了會 allocate 一段預定大小的記憶體之外, 會另外 allocate 一段記憶體 for <b>block_ele_t</b>
:::info
但是另外再 allocate `sizeof(size_t)` 是為了什麼呢?
:::
test_malloc 在==成功== allocate 一段位址後, 會作以下 6 件事
1. assign MAGICHEADER 給 new_block->magic_header
2. assign 預定 allocate size 這個數字給 new_block->payload_size
3. 透過 find_footer 取得 footer 的位址, 這個位址代表這個struct的最底部的位址, 並不能單純透過 new_block->magic_header 這種方法取得, 所以要透過 find_footer
4. 在 footer 的位址放 MAGICFOOTER。這就是 test_malloc 會另外再 allocate `sizeof(size_t)` 的原因, ==因為要放 MAGICFOOTER==
5. 把這個block insert 到list的最前面
6. 傳回 new_block->payload 的位址, ==這就是 allocate 所得到的位址==
[ [`source`] ](https://github.com/butastur-rtos/lab0-c/blob/master/harness.c#L136)
```clike
new_block->magic_header = MAGICHEADER;
new_block->payload_size = size;
*find_footer(new_block) = MAGICFOOTER;
```
```clike
/* Given pointer to block, find its footer */
static size_t *find_footer(block_ele_t *b)
{
size_t *p = (size_t *) ((size_t) b + b->payload_size + sizeof(block_ele_t));
return p;
}
```
:::info
* find_footer 是用來找出 footer of block
* footer of block 如果不是 MAGICFOOTER, 就會觸發一個 error event
* 由於 malloc 所得到的位址實際上是透 test_malloc 所得到, 所以要先用 find_header 取得 pointer to block_ele_t (也就是 block_ele_t *b), 再用 pointer to block_ele_t 取得 footer (也就是 *find_footer(b))
:::
[ [`source`] ](https://github.com/butastur-rtos/lab0-c/blob/master/harness.c#L161)
```clike
block_ele_t *b = find_header(p);
size_t footer = *find_footer(b);
```
### 說明 qtest 的行為
* console.c
* qtest.c
#### console.c
* run_console
* add_cmd
#### qtest.c
* run_console
* do_xxx
* q_xxx
* show_queue
## 研究自動測試機制
* TDD
* Unit Test
## 提問
:::info
自動測試機制是否可以理解為 TDD?
:::
## 實驗環境
```shell
$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
$ cat /proc/version
Linux version 2.6.32-042stab133.1 (root@kbuild-rh6-x64.eng.sw.ru) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) ) #1 SMP Thu Aug 16 13:14:51 MSK 2018
$ cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 44
model name : Intel(R) Xeon(R) CPU E5630 @ 2.53GHz
stepping : 2
microcode : 21
cpu MHz : 2533.628
cache size : 12288 KB
physical id : 1
siblings : 8
core id : 0
cpu cores : 4
apicid : 32
initial apicid : 32
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good eagerfpu xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat dtherm pti retpoline tpr_shadow vnmi flexpriority ept vpid
bogomips : 5067.25
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management:
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 44
model name : Intel(R) Xeon(R) CPU E5630 @ 2.53GHz
stepping : 2
microcode : 21
cpu MHz : 2533.628
cache size : 12288 KB
physical id : 0
siblings : 8
core id : 0
cpu cores : 4
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 11
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good eagerfpu xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 popcnt aes lahf_lm ida arat dtherm pti retpoline tpr_shadow vnmi flexpriority ept vpid
bogomips : 5066.57
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management:
```
:::danger
持續更新中...
:::
###### tags: `sysprog2018`