contributed by < yutingshih
>
目前尚未在實體機器執行 GNU/Linux,因此暫時在 Apple M1 Mac 上使用 Lima VM,參考 Lima VM with a foreign architecture (slow mode)
使用 malloc
函式配置記憶體給新建立的 struct list_head
,並用 Linux kernel API 提供的 INIT_LIST_HEAD
將 head
初始化 (把 prev
和 next
設為自身) 後回傳。
在實作 q_free
函式時原本想用上課教過的 list_for_each
巨集來走訪 linked list 的每個節點並釋放,但仔細看 list_for_each
的實作和註解描述,卻發現沒辦法使用 Linux kernel 風格的間接指標的方式來處理節點的刪除
所幸在同一份標頭檔的下方不遠處有提供了另一個巨集 list_for_each_safe
,可以用兩個 list_head
指標來進行走訪,一個用來指向目前要刪除的節點,另一個指向目前還「安全」的節點,正好符合我們的需求
因此我的 q_free
實作如下:利用 list_for_each_safe
巨集從 head
開始循序走訪每個 list 節點,再用 list_entry
巨集取得包含 list_head
的資料結構的起始位址,再依序釋放 element_t
結構體中手動配置的記憶體 (先釋放 value
字串再釋放 elem
物件本身),為了避免釋放後的記憶體被存取所帶來的安全性議題,當記憶體空間被釋放後就馬上將指向該空間的指標歸零。
由於list_for_each_safe
是從標頭節點 head
的下個節點開始走訪,因此走訪結束後,還要記得釋放 head
指向的記憶體,並將 head
指標歸零。
element_t
的建構函式,用來讓 q_insert_head
和 q_insert_tail
呼叫以產生新的節點
呼叫建構函式 __e_new
來創建新的 element_t
節點,並使用 Linux API 中的 list_add
,來將新節點加入 linked list 的最前面 (head
節點的後面),並且在過程中檢查 head
是否為空或是新增節點時是否配置記憶體失敗。
呼叫建構函式 __e_new
來創建新的 element_t
節點,並使用 Linux API 中的 list_add_tail
,來將新節點加入 linked list 的最後面 (head
節點的前面),並且在過程中檢查 head
是否為空或是新增節點時是否配置記憶體失敗。
remove 操作必須要在串列至少有一個元素節點時才能進行,因此先確認標頭節點 head
存在且至少有一個元素節點,根據 queue.h
的描述,除了將節點自 linked list 移出之外,還需要將移除的節點回傳並將其儲存的字串複製到指定區域,需要注意的是,C 語言的字串是以零結尾的 (null-terminated),因此要記得在字串 sp
的最後補零,另外,由於移除的節點需要被回傳,為了避免非預期的記憶體存取,這裡使用 list_del_init
而非 list_del
來移除節點。
與 q_remove_head
類似,只是差別在於 q_remove_tail
移除的節點位於 linked list 的尾端,也就是 head
的前一個節點。
q_size
計算 linked list 中有幾個元素 (struct list_head
),若 head
為 NULL
代表 linked list 為空,則回傳 0
,否則走訪整個 linked list,走訪過程中利用計數器 len
計算經過幾個節點,最後回傳 len
。
利用快慢指標的技巧來找到位於 linked list 中間的元素,一開始兩個指標在同樣的起點,在每一輪的疊代中 fast
指標往前走兩步,slow
指標往前一步,代表 slow
一定在 fast
和 head
的中點,當 fast
走訪完整個 linked list,則 slow
剛好走到一半,剛好就是我們想要移除的節點,接著使用 list_del_init
移除 slow
節點,並將指向的記憶體空間釋放。
根據 queue.h
的描述:
一開始誤解了 q_delete_dup
函式的功能,以為它是要刪除所有具有重複字串的節點。也就是說,若有兩個節點存有相同字串,就刪除其中一個,留下另一個。因此,在解 LeetCode 81 時,我寫下了這樣的程式碼。
後來才發現我誤解了題目,其實正確的作法應是若有兩個節點帶有相同資料,就都要刪除。為避免其他人誤解函式功能,或許未來可以改進一下程式碼的註解。
而正確的實作如下:
這個實作可以順利通過 LeetCode 的所有測試,接著讓我們繼續將其修改為 Linux 雙向鏈結串列的版本,並好好處理記憶體的釋放
其中為了方便,新宣告一個巨集做字串的比對
把串列中相鄰兩個節點交換順序可以拆成兩個步驟:
因此 q_swap
把串列上相鄰節點兩兩一組做交換,可以透過走訪迴圈的同時重複上述兩個步驟來完成,實作如下:
調整一下可以讓我們的程式碼更加精簡
基於剛才對 list_move
和 list_move_tail
的理解,可以在走訪串列期間持續把節點加入 head
的後面,來達成 q_reverse
的效果
每經過 k 個節點,就重新設定一次 group_head
,作為接下來要插入節點的基準
不要急著張貼程式碼,你應該闡述你的想法、中間的嘗試,和研讀過的相關材料,「開發紀錄」著重過程。
:notes: jserv