contributed by < YiChianLin
>
queue.c 僅提供界面(interface) 但尚未有完整程式實作,需要由學員補完並逐步精進,包含以下:
q_new
: 建立新的「空」佇列;q_free
: 釋放佇列所佔用的記憶體;q_insert_head
: 在佇列開頭 (head) 插入 (insert) 給定的新節點 (以 LIFO 準則);q_insert_tail
: 在佇列尾端 (tail) 插入 (insert) 給定的新節點 (以 FIFO 準則);q_remove_head
: 在佇列開頭 (head) 移去 (remove) 給定的節點;q_release_element
: 釋放特定節點的記憶體空間;q_size
: 計算目前佇列的節點總量;q_delete_mid
: 移走佇列中間的節點;q_delete_dup
: 在已經排序的狀況,移走佇列中具備重複內容的節點;q_swap
: 交換佇列中鄰近的節點;q_reverse
: 以反向順序重新排列鏈結串列,該函式不該配置或釋放任何鏈結串列節點,換言之,它只能重排已經存在的節點;q_sort
: 以遞增順序來排序鏈結串列的節點;malloc
新配置記憶體空間INIT_LIST_HEAD
將新建立的 list_head
連結成環狀的結構circlar doubly-linked list
的 head
節點串接再一起malloc
會失敗的問題,在 trace-12-malloc.cmd
會產生 Segmentation fault 的問題產生,所以必須考慮到 malloc
失敗的情形,將程式進行修改trace-12-malloc.cmd
文件內容 option malloc 50
敘述會將 malloc
的失敗機率提升至 50%head
是否 malloc
成功head
節點並回傳,若失敗則釋放配置失敗的空間並為傳 NULL
函式功能:
思考方式:
head
是否為 NULL
(注意: empty
不等於 NULL
)NULL
佇列 : 是指標值為 NULL
;head
) 的值為 NULL
;list_entry
可找出每個 linked list 資料的起始位址ptr (element_t *ptr)
取得節點資料的位址queue.c
的 q_release_element()
將結構 element_t
的節點裡的 *value
與 linked list 的指標釋放,參考以下的 q_release_element()
程式碼node
釋放後,再將 linked list 的 head
釋放程式運作:
1.先判斷 linked list 的 head
(也就是引數中的 list_head *l
) 是否為 NULL
(注意 : 若是 empty
則判斷式不成立)
2.使用 tmp
指標走訪每個節點
3.建立 ptr (element_t *ptr)
取得節點資料的位址
4.tmp
指標走訪至下一個點
5.ptr
取得節點資料後再使用 q_release_element()
釋放記憶體空間
6.直到每個 node
釋放結束最後將 l
(注意 : l
為沒有 value 的指標) 釋放
API
進行實作將 container_of
更換成 list_entry
, 雖然兩個函式功能相同,但是在協作上的考量上要能有統一的規範!
除了列出程式碼,你應該說明程式運作和後續可改進之處。
:notes: jserv
q_release_element()
element_t
的資料釋放,分別有 element_t
內部的 char
與 element_t
本身的指標node
,將資料新增且配置記憶體list_add
函式進行新增 node
head
是否為 NULL
(如果是 empty
情況也可以執行)element_t *new
的記憶體,並也配置資料的記憶體 char* new->value
長度為 strlen(s)
strncpy()
將 s
複製到 new->value
中(注意: 不能使用 strcpy()
這個函式是不安全的函式不可使用)list_add()
將新 node
加入 list
中ISO/IEC 9899:1999
7.21.2.4 The strncpy function
char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
Description : The strncpy function copies not more than n characters (characters that followanull character are not copied) from the array pointed to by s2 to the array pointed to by s1.
If the array pointed to by s2 is a string that is shorter than n characters, null characters are appended to the copy in the array pointed to by s1, until n characters in all have been written.
可以得知函式的使用方式為,將
s2
字符複製n
個數量到s1
中
list_add()
函式*next = head->next;
,將 next
指向 head->next
也就是 A
next->prev = node;
,將 A->prev
指向 new_node
node->next = next;
,將 new_node->next
指向 A
node->prev = head;
,將 new_node->->prev
指向 head
也就是指回 A
的前一個head->next = node;
,將 head->next
指回 new_node
完成 circular doubly-linked list
新加入一個 node
的操作s
是否為 NULL
new->value
在配置記憶體的時候要多一個空間給結尾符號 \0
malloc
失敗的 node
指標在測試 trace-12-malloc.cmd
時,發現上述程式碼會有問題,使用 malloc
失敗的指標佔用記憶體,需要將未配置成功的指標釋放,且將輸入的字串加入結尾符號 \0
,因此經過調整
注意共筆書寫規範:中英文間用一個半形空白字元區隔
:notes: jserv
好的,學生會再多注意共筆書寫規範!YiChianLin
if (new && s)
判斷 new
是否能不能 malloc
成功,以及判斷 s
資料是否為 NULL
new->value = malloc((strlen(s) + 1) * sizeof(char))
*(new->value + strlen(s)) = '\0';
free(new)
將有可能配置失敗的記憶體釋放q_insert_tail()
相似list_add_tail()
函數list_add_tail()
函式list_add
相似,差別在於對 head->prev
操作同理在 q_insert_tail()
發現與 q_inset_head()
一樣的問題,並加以修改
函式功能:
思考方式:
list_entry
函式進行找出節點 element_t
的資料head->next
(注意:移除 remove
不等於刪除 delete
)bufsize
與欲將移除的資料比較長度sp
程式運作方式:
linked list
是否為 NULL
或是 empty
(只剩下單一的 head
)bufsize
與 rm_ele->value
之長度,應修正為將 bufsize - 1
字串長度複製給 sp
並且在字串的尾端加入結尾符 \0
,所以將其改寫q_removed_head
相似,相異之處在於移除目標改為 head->prev
q_removed_head
遇到的問題相同,將程式加以修正list_for_each
可走訪 linked list
的每一個點進行長度的計算list_for_each
函式for
迴圈透過 node
指標指向 head->next
也就是第一個元素,所以在迭代的次數為"linked list 的元素個數",並不包含空資料的 head
程式功能:
思考方式:
head
走訪到該次數的節點(中間點)程式運作
linked list
是否存在或是為單一的 head
,若是其中一條件符合則會回傳 false
q_size
函式先得出,中間元素的位置,且要判斷偶數或是奇數for
迴圈走放置中間點list_del
將中間點移除( remove
)list_entry
取得 element_t
的指標q_release_element
,先將 element_t->value
(內部資料)釋放,再將 element_t
本身的指標也釋放程式運作前:
C
節點C
C
的資料q_size
函式,而是利用兩個不同速率的指標找到中間點,可增加程式的效率找尋中間的節點方式好比龜兔賽跑的形式,當兔子到終點的時候,烏龜只跑了一半
first
、 second
指標指向 head->next
first != head && first->next != head
直到 first
跑完整個 linked list
, 所以 second
就會落在中間點的位置while
迴圈中 first
每次都移動兩個節點, second
每次移動一個節點C
C
的資料remove
)再刪除( delete
),直到下一個點不是重複,將起始位置的節點刪除head
用以下簡單的範例解釋:
tmp
指標指到第一個 node
, tmp->next
指標指到第二個 node
,利用判斷是否有重複元素的指標 if_dup
指向 NULL
tmp
指標指到 A
, tmp->next
指標指到 B
所以兩個元素的值不同,因此兩個指標走放置下一個節點,由於 tmp
與 tmp->next
的元素相同,使 if_dup
記住 tmp
的位置tmp->next
也就是重複的元素 remove
與 delete
tmp
走訪至下一個節點,將有重複點的起始位置 if_dup
節點刪除,重複進行上述動作直到把具有重複的節點都刪除linked list
的連接串列,而是對 element_t
內部的 element_t->value
進行交換linked list
長度的一半次數forward
與 forward->next
每次指向鄰近的兩個節點list_entry
取得 element_t
內部 value
值,再透過暫存的 char *tmp
,將資料進行交換forward
與 forward->next
兩個指標前進到下兩個元素進行交換直到走訪全部的 linked list
與 StevenChou43 和 Risheng1128 同學討論後,發現這樣資料的交換方式對記憶體的處理會造成負擔,需要多配置暫存的記憶體空間,造成效率不彰的問題
list_del
與 list_add
進行操作first
與 second
指標指到前兩個元素first
指標的元素 A
first
指標移除後,再從 second
指標的位置使用 list_add
加入 A
second = first->next
second
指到 C
, first = first->next->next
first
移動兩個節點到 second
前面一個節點,持續上述的流程直到所有的節點交換完成head -> 1 -> 2 -> 3 -> 4 -> head
經反轉後, head -> 4 -> 3 -> 2 -> 1 -> head
prev
、 cur
、 next
cur
指標走訪至每個節點改變其 cur->next
、 cur->prev
指向的位置,分別指向 prev
與 next
指標的位置linked list
cur->next
從 A
改指到 C
, cur->prev
,從 C
改指到 A
pre
走訪至 cur
的位置,cur
走訪至 next
的位置, next
走訪至 next->next
位置,完成一次的循環動作,直到將所有節點的反轉完成程式功能:
思考方式:
NlogN
)分割兩串 linked list
自定義函式 sep_list
程式運作
找尋的方式參考至 SteveChou同學 開發紀錄找尋中間點的方式
fast
與 mid
從 linked list
的頭尾兩端進行走訪,直到兩個指標相遇或是中間隔一個節點的時候,方可找到中間節點的位置head
與 mid
中間點分別為兩串資料的起使節點,設定為left
與 right
linked list
分割成兩段,分割方式使用圖解的方式說明:left->prev = right->prev;
, left->prev
指向 A
, left->prev->next = left;
也就是 A->next
指回 head
node->next = right;
,,也就是 C->next
指向 B
,最後 right->prev = node;
也就是 B
指回 C
,最後得到分割後的兩段 linked list
list_del_init
函數自成一個指向自己的節點,最後進入 merge_list
函式中將資料進行合併merge_list
函式說明sep_list
函式中得到兩半部分的 linked list
NULL
作法類比至單向 linked list
的方式,為判斷式的依據head
與 tmp
指標作為合併資料用的暫存點head
(合併後的資料的起始位置)未建立,使用 strcmp
判斷左右邊的第一個資料,若 return < 0
則使 left
的第一個資料當作是 merge
後資料的 head
,若 return > 0
則反之head
(合併後的資料的起始位置)已建立,則使用 list_add_tail
把判斷出較小的資料加入 linked list
中merge
後的結果7.21.4.2 The strcmp function
Description : The strcmp function compares the string pointed to by s1 to the string pointed to by s2.
Returns : The strcmp function returns an integer greater than, equal to, or less than zero, pointed to by s2.
可用來比較字串間的大小,若
s1
較小會return < 0
的數值,而s1
較大則會return > 0
的數值
q_sort
函式說明
linked list
未含有資料的 head
節點分離出linked list
經過 sep_list
中透過 merge sort
方式,先將資料分段在兩兩合併linked list
再將 head
加入qtest
提供新的命令 shuffle
,允許藉由 Fisher–Yates shuffle 演算法
,對佇列中所有節點進行洗牌 (shuffle
) 操作qtest.c
檔案中加入 do_shuffle
與 q_shuffle
函式linked list
是否為 NULL
、 empty
、 singular
length
為總函式的操作次數(交換幾輪),再來是 count
每次的交換後最後一個元素的位置,每經過一輪即遞減透過, rand() % (count - 1) + 1
獲取每一輪的隨機亂數位置head
, tmp
指標指到隨機位置的前一個位置,主要用來獲得要加入交換來的位置, tail
指標則指到每一輪要交換的為最後一個元素, rand_ptr
為隨機位置的元素指標rand_ptr
隨機的元素移除後再加入在 tail
後方tail
元素加入至 rand_ptr
被交換前的位置,也就是 tmp
指標元素的下一個head
準備進行下一輪的交換qtest.c
中新增 shuffle
命令,觀察其他的宣告命令方式可以得知函式的兩個引數,分別為 argc
與 argv
分別為輸入 command
的引數數量與引數內輸入字元dophin
argc = 3
, argv[0] = ih
、 argv[1] = dophin
、 argv[2] = 5
shuffle
命令,變數為 argc = 1
, argv[0] = shuffle
,進入 do_shuffle
會先判斷命令的輸入是否為一個引數,若命令錯誤會顯示 shuffle takes no arguments
的錯誤訊息l_meta.l
在 qtest.c
中可觀察為整個 linked list
所以判斷 l_meta.l
是否存在q_shuffle
的函式中進行打亂元素的功能show_queue
函式將 shuffle
後的結果顯示./qtest
進入命令模式,輸入以下指令測試RAND
指令填入隨機 6 個元素,測試命令的運作情況merge_final
函式中,處理了不平衡的兩個串列,為了減少過多不必要的迭代次數,以下是原文的附註list_sort.c
中,主要處理了非 2 倍數的串列,會進行 padding 的動作,引述自原文,在 mergesort 中會希望最差能有 2 : 1 的資料比例進行,主要能避免 cache thrashing 的情況產生,可避免 CPU 的效率急速下降__builtin_expect
為 gcc 引入,目的提高 cpu 的效率,讓 cpu 可以預先取出下一條指令,減少 cpu 的等待時間,在參數中 !!(x)
將 (x)
轉換成 bool 值,可以得到 true/false
所以在執行上 likely(x)
會用 if 的敘述的機會變大list_cmp_func_t
的宣告,加入了 4 、 5 行的敘述,參考至 list_sort.h對 list_cmp_func_t
的宣告__attribute__((nonnull(2,3)))
確保在 2 3 位的引數中不為空指標The nonnull attribute specifies that some function parameters should be non-null pointers
文件
這裡參考了 lanser同學的筆記 加入了對引數的資料的比較方式,利用 strcmp
比較 merge list
中的兩元素的值(使用 list_entry
取出)
list_sort.c
內部的函式說明:補完上述的一些宣告就可以使用 lib/list_sort.c 的函式做使用
trace
目錄下新增兩個測試的的檔案分別為 trace-18-sort.cmd
與 trace-19-sort.cmd
trace-18-sort.cmd
內容如下,主要測試以 RAND
指令給定下的隨機資料進行比較,分別進行 100000 、 500000 、 1000000 筆資料的比較perf
工具,參考至 perf 的使用說明
Data Number | 100000 | 500000 | 1000000 |
---|---|---|---|
list_sort | 0.029 | 0.318 | 0.721 |
q_sort | 0.034 | 0.345 | 0.807 |
time improved | 17.24% | 8.5% | 11.92% |
總合上述的結果進行一點分析比較:
list_sort
的方法在亂數資料的排序下,比自己寫的 q_sort
都有約 10% 的速度提升cache-misses
與 instructions
上也 list_sort
的資源使用上也少了一些,整體是比較有效率的trace-19-sort.cmd
內容如下,主要測試以給定固定的兩種重複性資料(分別為 gerbil 與 dolphin),分別進行 100000 、 500000 、 1000000 筆資料的測試
Data Number | 100000 | 500000 | 1000000 |
---|---|---|---|
list_sort | 0.010 | 0.085 | 0.172 |
q_sort | 0.014 | 0.131 | 0.282 |
time improved | 40% | 54.11% | 63.95% |
list_sort
很明顯的就比 q_sort
所花的時間還要短,而且在越多筆資料的表現上,時間上的差距會越來越明顯(40% -> 54.11% -> 63.95%),可見在 list_sort
對於重複性或預排好的資料處理的速度快了許多cache-misses
與 instructions
上 list_sort
的資源使用上也相對使用的較少