Мне нравится этот пример кода "реального мира" указателя на использование указателя, в Git 2.0 commit 7b1004b :
Линус однажды сказал:
Я бы хотел, чтобы больше людей понимали действительно базовый низкоуровневый код. Не большие, сложные вещи, такие как поиск по имени без блокировки, но просто хорошее использование указателей на указатели и т. Д.
Например, я видел слишком много людей, которые удаляли односвязную запись списка, отслеживая запись «prev», а затем удаляли запись, выполняя что-то вроде
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
и всякий раз, когда я вижу такой код, я просто говорю: «Этот человек не понимает указателей». И это, к сожалению, довольно часто.
Люди, которые понимают указатели, просто используют " указатель на указатель ввода " и инициализируют его адресом list_head. И затем, проходя по списку, они могут удалить запись без использования каких-либо условий, просто выполнив команду
*pp = entry->next
Применение этого упрощения позволяет нам потерять 7 строк этой функции даже при добавлении 2 строк комментария.
- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;
Крис указывает в комментариях к видео 2016 года " Проблема двойного указателя Линуса Торвальдса " Филип Буак .
kumar указывает в комментариях сообщение в блоге " Линус о понимании указателей ", где Гриша Трубецкой объясняет:
Представьте, что у вас есть связанный список, определенный как:
typedef struct list_entry {
int val;
struct list_entry *next;
} list_entry;
Вам нужно перебрать его от начала до конца и удалить определенный элемент, значение которого равно значению to_remove.
Более очевидный способ сделать это будет:
list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;
while (entry) { /* line 4 */
if (entry->val == to_remove) /* this is the one to remove ; line 5 */
if (prev)
prev->next = entry->next; /* remove the entry ; line 7 */
else
head = entry->next; /* special case - first entry ; line 9 */
/* move on to the next entry */
prev = entry;
entry = entry->next;
}
То, что мы делаем выше, это:
- итерация по списку до записи
NULL
, что означает, что мы достигли конца списка (строка 4).
- Когда мы сталкиваемся с записью, которую мы хотим удалить (строка 5),
- присваиваем значение текущего следующего указателя предыдущему,
- , исключая текущий элемент (строка 7).
Существует особый случай выше - в начале итерации нет предыдущей записи (prev
is NULL
), поэтому для удаления первой записи в списке необходимо изменить саму голову (строка 9 ).
Линус говорил, что приведенный выше код можно упростить, сделав предыдущий элемент указателем на указатель, а не просто указатель .
Код выглядит следующим образом:
list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;
while (entry) {
if (entry->val == to_remove)
*pp = entry->next;
pp = &entry->next;
entry = entry->next;
}
Приведенный выше код очень похож на предыдущий вариант, но обратите внимание, что нам больше не нужно следить за частным случаем первого элемента списка, поскольку pp
не является NULL
в начале. Просто и умно.
Кроме того, кто-то в этой теме заметил, что причина в том, что это лучше, в том, что *pp = entry->next
атомарен. Это, безусловно, НЕ атомарный .
Вышеприведенное выражение содержит два оператора разыменования (*
и ->
) и одно присваивание, и ни одна из этих трех вещей не является атомарной.
Это распространенное заблуждение, но, увы, почти ничего в C не следует считать атомарным (включая операторы ++
и --
)!