# 2016q1 Homework2 (B) unit-test 作業要求 (B) * 學習GDB (GNU Debugger),透過GDB進行unit testing * 在 GitHub 上 fork [unit-tests](https://github.com/embedded2016/unit-tests),並依據 [第三週](https://embedded2016.hackpad.com/ep/pad/static/kpfgwU6YgOF) 課堂 code review 的認知,指出給定程式不足之處 * 補強給定程式的 swap() 和 bubble_sort() 函式,並且研究如何用 GDB 進行自動測試 * 實做 Merge sort,可參見 [競技程式設計課程的解說](http://wiki.csie.ncku.edu.tw/acm/course/Review1),需要提供以下檔案: * merge.c: merge_sort() 函式的實做 * test-merge.sh: 測試 merge_sort() 函式的 GDB script * data-merge.in: 用來測試的輸入資料,原則上與 data-bubble.in 相仿 * 將你學習 GDB 的過程、指出原本程式不足之處 (程式碼寫出來就是給你改進的,可不是給你背誦用!)、如何修正和強化程式,以及在實做 merge sort 的各項啟發,紀錄於 「[作業區](https://embedded2016.hackpad.com/2016q1-Homework-2-v37rXPJjRlW)」,記得標注「開發紀錄(B)」 ## GDB學習 先來測試跟學一些指令,編譯程式需要加上 -g,執行加上 gdb ``` gcc -g -o swap swap.c  gdb ./swap ``` 指定breakpoint,因爲只是測試先,所以我在程式裏加上了main ``` (gdb) b main Breakpoint 1 at 0x400733: file swap.c, line 99. ``` 開始執行 ```javascript= (gdb) r Starting program: /home/tempo/Desktop/hw2/unit-tests/swap  Breakpoint 1, main () at swap.c:99 99            return 0; (gdb) p $head       //建立一個head,p = print $1 = void (gdb) source scripts/create_list.gdb     //讀入gdb script (gdb) source scripts/print_list.gdb  (gdb) source scripts/free_list.gdb  (gdb) create_list $head 4    //利用剛剛的head建立一個4個node的list (gdb) printf_list $head    //輸出list,結果是0,1,2,3 $28 = (List *) 0x602010 $29 = 0 $30 = (struct List_node *) 0x602030 $31 = 1 $32 = (struct List_node *) 0x602050 $33 = 2 $34 = (struct List_node *) 0x602070 $35 = 3 $36 = (struct List_node *) 0x0 /* 測試程式的swap,結果應該要爲2,1,0,3 */ (gdb) p $head = swap($head ,$head , $head->next->next)     $37 = (List *) 0x602050 (gdb) printf_list $head $38 = (List *) 0x602050 $39 = 2 $40 = (struct List_node *) 0x602030 $41 = 1 $42 = (struct List_node *) 0x602010 $43 = 0 $44 = (struct List_node *) 0x602070 $45 = 3 $46 = (struct List_node *) 0x0 ``` ## Swap.c 程式碼分析 1. 剛開始的 If 應該要用 || 而不是 && ```clike= if (!head || (node_1 == NULL) || (node_2 == NULL) || (node_1 == node_2)) return head; ``` 尋找node的迴圈應該設定一個break condition已節省時間,然後判斷是不是head的部分也可以先做,如果有其中一個是head,那就只需尋找另一個node就好了。因此就在尋找node的迴圈裏加入多一個condition。另外還有判斷circular linked list,也防止無限迴圈。 好奇circular linked list的定義,如果尾巴跟中間的節點相連這也算嗎? ```clike= if (head == node_1) { pre_node_1 = NULL; num_pre_node_1_and_node_2 = num_pre_node_1_and_node_2 + 1; } if (head == node_2) { pre_node_2 = NULL; num_pre_node_1_and_node_2 = num_pre_node_1_and_node_2 + 1; } /* 這裏加多一個判斷num_pre_node_1_and_node_2 是不是小於2 */ while ( (head && head->next) || num_pre_node_1_and_node_2 < 2 ) { if (head->next == node_1) { pre_node_1 = head; num_pre_node_1_and_node_2 = num_pre_node_1_and_node_2 + 1; /* 判斷circular,不過也只能判斷出尾跟頭相連罷了 */ if(node_1->next == _head) is_circular = true; } else if (head->next == node_2) { pre_node_2 = head; num_pre_node_1_and_node_2 = num_pre_node_1_and_node_2 + 1; if(node_2->next == _head) is_circular = true; } head = head->next; } ``` 3.修改一些地方跟加上做circular的部分 ```clike= if (pre_node_1 == NULL) { pre_node_2->next = node_1; tmp_node = node_1->next; /* circular linked list */ if(is_circular) node_1->next = node_2; else node_1->next = node_2->next; node_2->next = tmp_node; return node_2; } ``` 最後檢查輸出有沒有問題(一部分): ```javascript= $28 = "old_list" $29 = (List *) 0x602010 $30 = 0 $31 = (struct List_node *) 0x602030 $32 = 1 $33 = (struct List_node *) 0x602050 $34 = 2 $35 = (struct List_node *) 0x602070 $36 = 3 $37 = (struct List_node *) 0x0 $38 = (List *) 0x602010 $48 = "change 1  3"        //換掉第一個跟第三個node $49 = "new_list" $50 = (List *) 0x602050 $51 = 2 $52 = (struct List_node *) 0x602030 $53 = 1 $54 = (struct List_node *) 0x602010 $55 = 0 $56 = (struct List_node *) 0x602070 $57 = 3 $58 = (struct List_node *) 0x0 $59 = (List *) 0x602050 ``` ## Bubble.c 程式碼分析 1\. 首先是刪除一些不必要的code,第一個迴圈計算list的長度,裏面還有做了一點sort,在這裏的這個sort是不必要的,所以就把它刪了。還有加上circular的判斷,也避免無限迴圈: ```clike= while (sub_head && sub_head->next) { num_list = num_list + 1; /* Circular list  */ if(sub_head->next == *head) break; sub_head = sub_head->next; } ``` 2.再來是以下這行有點奇怪,首先一開始就有判斷head是不是空的,也可以利用剛剛計算的    num_list來確認list到底是不是空的,所以這個有點不必要 ` for (*head && (*head)->next; i < sub_for_times; i++) ` * 因此我把它改成利用num_list來做最後的sorting部分,也把sub_for_times和sub_for_Max變數給刪了 ```clike= for (i = num_list; i > 0; i--) { sub_head = *head; pre_sub_head = head; for (sub_i = 0; sub_i < i; sub_i++) { /*    這裏我把大的號碼排去後面    */ if (sub_head->value > sub_head->next->value) { sub_head = swap(sub_head,sub_head,sub_head->next); *pre_sub_head = sub_head; } pre_sub_head = &((*pre_sub_head)->next); sub_head = sub_head->next; } } ``` 都修改好後就執行看看結果,發現它沒有讀到測資中第一行的3,反而讀到下一行的12,也導致後面的結果亂掉了。看了一下test-bubble.sh檔後,發現第一個while會讀到一個var,導致下一次讀的時候是12。因此便在data-bubble.in中加多一個空行就行了。 ```clike= while  (read var)  do read var list_length=$var ``` 最後在查看結果(一部分): ```javascript= $27 = "test begin" $28 = "old_list" $29 = (List *) 0x602030 $30 = 12 $31 = (struct List_node *) 0x602050 $32 = 4 $33 = (struct List_node *) 0x602070 $34 = 5 $35 = (struct List_node *) 0x0 $37 = "new_list"       //已排序好的list $38 = (List *) 0x602050 $39 = 4 $40 = (struct List_node *) 0x602070 $41 = 5 $42 = (struct List_node *) 0x602030 $43 = 12 $44 = (struct List_node *) 0x0 ``` ## Merge sort 實作 利用很普通的演算法來實作,先計算list的長度,在進入遞回跟merge進行sorting: ```clike= void merge_sort(List **head) { if (head == NULL || (*head==NULL)) return; int n = 0; List *_head; _head = *head; while (_head && _head->next) { n++; /*  Circular linked list  */ if (_head->next == *head) break; _head = _head->next; } Sort(head, 0, n); } void Sort(List **list, int low, int high) { if(high > low) { Sort(list, low, (low+high)/2); Sort(list, ((low+high)/2)+1, high); Merge(list, low, high); } } ``` 再來是其它function的詳細實作就不po在這邊了,最後產生自己的測資,考慮到執行時間的關係,只產生100個,每筆測資也只有50個亂數: ```clike= for(i=0; i<100; i++) { n = rand() % 50 + 1; fprintf(fp,"%d\n",n); for(j=0; j<n; j++) { int value = rand() % 1000 + 1; fprintf(fp,"%d ",value); } } ``` 最後查看結果(一部分): ```javascript= $51 = "test begin" $52 = "old_list" $53 = (List *) 0x602030 $54 = 949 $55 = (struct List_node *) 0x602050 $56 = 814 $57 = (struct List_node *) 0x602070 $58 = 461 $59 = (struct List_node *) 0x602090 $60 = 583 $61 = (struct List_node *) 0x6020b0 $62 = 954 $63 = (struct List_node *) 0x6020d0 $64 = 346 $65 = (struct List_node *) 0x0 $67 = "new_list"        //Sorted $68 = (List *) 0x602030 $69 = 346 $70 = (struct List_node *) 0x602050 $71 = 461 $72 = (struct List_node *) 0x602070 $73 = 583 $74 = (struct List_node *) 0x602090 $75 = 814 $76 = (struct List_node *) 0x6020b0 $77 = 949 $78 = (struct List_node *) 0x6020d0 $79 = 954 $80 = (struct List_node *) 0x0 ```