<!--@page { margin: 2cm }PRE.cjk { font-family: "DejaVu Sans", monospace }P { margin-bottom: 0.21cm }A:link { so-language: zxx }-->
核心的鏈表list_head設計相當巧妙。今天我說一下對list_head鏈表的遍曆時如何刪除元素。
鏈表遍曆時,如果刪除當前元素,一般都是會出錯的。在所有語言的各種庫中的鏈表都是如此。list_head也一樣。
<!--@page { margin: 2cm }P { margin-bottom: 0.21cm }A:link { so-language: zxx }-->
如,在java的遍曆中刪除當前元素,會拋出java.util.ConcurrentModificationException異常。
見:《Java中如何刪除一個集合中的多個元素》http://blog.csdn.net/shendl/archive/2007/12/28/1999907.aspx
一文。
使用list_for_each遍曆鏈表,如果使當前元素脫鏈,那麼系統就會毫不留情的crash掉。什麼提示資訊都沒有。因此這類bug非常難以定位。
list_for_each源碼:
/** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */#define list_for_each(pos, head) / for (pos = (head)->next; prefetch(pos->next), pos != (head); / pos = pos->next)
list_del脫鏈元素後,會把next和prev分別賦值為:
/* * These are non-NULL pointers that will result in page faults * under normal circumstances, used to verify that nobody uses * non-initialized list entries. */#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA
list_del_init脫鏈元素後,會把next和prev都設定為自己。
因此,在list_for_each中刪除當前元素後,就無法正確找到鏈表的下一個元素。
如果要在遍曆list_head鏈表時,刪除當前元素,那麼就必須使用list_for_each_safe函數而不能使用list_for_each函數。
list_for_each_safe源碼:
/** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */#define list_for_each_safe(pos, n, head) / for (pos = (head)->next, n = pos->next; pos != (head); / pos = n, n = pos->next)
這個函數比list_for_each函數多了一個n參數。這個參數也是list_head類型的。
它儲存下一個元素,這樣就可以安全的刪除當前元素,不會造成找不到後續元素的情況發生。
在迴圈結束時,pos指向n元素,而不是指向pos的next元素。因為pos脫鏈後,pos元素的next可能已經是null 指標,或者是LIST_POISON1
這個無意義的值了。
如果list是空的,那麼pos=n後,仍然等於head,遍曆就此結束了!
因此,使用lisf_for_each_safe函數遍曆list_head鏈表,就可以安全地刪除當前元素了。