核心的鏈表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://www.bkjia.com/kf/201105/92049.html 一文。
使用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鏈表,就可以安全地刪除當前元素了。