contributed by < XDEv11
>
queue.[ch]
實作queue_t
結構定義為了能實作出 O(1) 的 q_size
與 q_insert_tail
,這邊直接紀錄佇列的大小與尾端。
並且定義常數 QUEUE_STRLEN_MAX
來限制字串最大長度,方便後面使用 *n*
的函式,避免 <string.h>
中某些不安全的函式。
CERN Common vulnerabilities guide for C programmers。
tail
是指標的指標,指向 head
或是尾端節點的 next
。
因此要在尾端放入新節點,只需要指定新節點的指標到 *tail
即可。
若傳入參數為 NULL
佇列,則依據函式要求做特殊處理。(e.g., No effect or Return false)
q_new
q_free
這邊先寫一個輔助函數,建立新的節點,並把 s
中的字串複製過去。這邊使用 *n*
的函式,避免造成緩衝區溢位 (buffer overflow) 的問題。
q_insert_head
對空佇列加入元素時,由上圖例可知,需要特別去修改 q->tail
。
q_insert_tail
q->tail
改為指向新元素的 next
。
q_remove_head
這邊一樣使用 *n*
的函式來進行字串複製。
跟 q_insert_head
一樣需要注意的是,當它變為空佇列時,需要特別去修改 q->tail
。
q_size
q_reverse
逐步地把每個元素加入 newh
的前端即可,最後要先把 q->tail
指向 q->head
(即新的尾端) 的 next
。
q_sort
這邊使用 merge sort 演算法來實作 q_sort
,時間複雜度是 ),較為穩定。
這邊的 strcmp 就沒有使用 *n*
的函式,因為在程式中可以確保節點裡面的字串會是 null-terminated。
透過 mid
每次移動一步以及 tail
每次移動二步來尋找鏈結串列的中心, mid
及 tail
皆為指標的指標,不過這邊的意義比較不同,代表的位置不是指向的節點 (*mid
/ *tail
),而是當指向某個節點的 next
(或是 head
),實際上就是代表在那個節點 (或是在 head
),因此 tail
可以透過 *tail
判斷是否有下一個節點,也就是是否到達尾端。
一開始 mid
跟 tail
都在 head
上,
移動二次後,
可以看到此時 mid
剛好在前半部的尾端,指標的指標在這裡除了可以得到後半部的前端 (*mid
),也是為了方便把二部分分開 (*mid = NULL;
)。
merge_sort
的參數 list
也使用指標的指標,直接修改真正的指標,一方面因為經過排序後,舊的指標沒有什麼代表意義及用途,另一方面也避免新的指標未被記錄而造成記憶體流失 (Memory leak)。
呼叫 merge_sort
進行排序後,需重新找一次尾端節點,並把 q->tail
指向它的 next
。
透過 $ make SANITIZER=1
開啟 Address Sanitizer 後,
執行 $ make test
,得到以下錯誤,
執行 qtest
再於命令提示列輸入 help
命令,得到以下錯誤,
可以看到程式在 console.c:369
以及 console.c:307
出問題,程式碼如下,
可以發現都與 plist
有關,型態是 param_ptr
,到 console.h
中可找到相關型態定義,
而錯誤訊息中也發現是跟 simulation
以及 echo
二個變數有關,來看看 console.c
中相關的部分。
那再來看看 add_param
到底在幹嗎?
原來它就是在 param_list
的尾端加入一個新的參數,但是 valp
的型態為 int *
,而呼叫時被直接轉型,導致後續透過 int
的指標 dereference 時發生記憶體錯誤。
為了不改變其它結構定義與函數原型,最簡單的解決辦法就是把二個 bool
變數改為 int
型態。
執行 $ make valgrind
,會得到以下訊息。
qtest.c:769
呼叫 linenoiseHistoryLoad()
,又會呼叫 linenoiseHistoryAdd()
,在 linenoise.c:1224
附近可以看到以下的程式碼,
配置了記憶體給 history
。
而在 linenoise.h
中,我們可以看到 freeHistory()
這個函式, linenoise.c
中的程式碼如下,
可以得知應透過這個函式來釋放 history
的記憶體。
先看一下 freeHistory()
會在哪邊被呼叫,追蹤過程中可以看到 atexit()
。
The atexit() function registers the given function to be called at normal process termination, either via exit() or via return from the program's main(). Functions so registered are called in the reverse order of their registration; no arguments are passed.
在 console.c
中,
只有 Raw mode 的時候會呼叫到 linenoise()
,也只有這時候需要用到 history
的相關函式,因此把 qtest.c:769
附近程式碼改成,
即可解決問題。
linux2021