Установка указателей на ноль для предотвращения утечки памяти в Golang

Я учусь ходить, и в качестве упражнения я хотел реализовать связанный список. Для справки я посмотрел на официальный код Go (https://golang.org/src/container/list/list.go). Одна вещь, которая застряла со мной такие строки:

   108  // remove removes e from its list, decrements l.len, and returns e.
   109  func (l *List) remove(e *Element) *Element {
   110      e.prev.next = e.next
   111      e.next.prev = e.prev
   112      e.next = nil // avoid memory leaks
   113      e.prev = nil // avoid memory leaks
   114      e.list = nil
   115      l.len--
   116      return e
   117  } 

Мне любопытно, как установка указателей на nil в этом случае избежать утечки памяти? Если возможно, я хотел бы построить программу, которая имеет этот недостаток, и увидеть ее при профилировании с помощью pprof (я бы использовал модифицированную версию список.перейдите без этой настройки указателя nil).


для ясности ответа: если один из узлов имеет внешний указатель на него, то все соседние удаленные узлы будут иметь активную ссылку через этот указатель и не будет удален. enter image description here

  1. мы создаем внешний указатель, указывающий на Node2
  2. удаляем узлы 2-4 из списка
  3. вы ожидали бы в этот момент только для узла 1,2 & 5, чтобы быть живым и все остальное ГХ-е изд. Однако из-за Node2 все еще указывая на Node3 & etc. вся цепь остается не собранной.

2 ответов


ваши предположения верны. Если есть группа указателей, указывающих друг на друга, но нет ссылки / указателя на любого члена этой группы, группа будет обнаружена как недоступная сборщиком мусора и будет освобождена должным образом.

но объяснение утечки памяти просто. Мы можем получить list.Element обертки из списка, которые содержат несообщаемые Element.next и Element.prev указатели на следующий и предыдущий элементы список.

при удалении элемента из списка, если эти указатели не будет иметь значение nil, они будут содержать ссылки на следующие и предыдущие оболочки элементов, включая значения, связанные с этими элементами.

посмотреть этот пример:

var e2 *list.Element

func main() {
    listTest()
    fmt.Println(e2.Value)
    // At this point we expect everything from the list to be
    // garbage collected at any time, we only have reference to e2.
    // If e2.prev and e2.next would not be set to nil,
    // e1 and e3 could not be freed!
}

func listTest() {
    l := list.New()
    e1 := l.PushBack(1)
    e2 = l.PushBack(2)
    e3 := l.PushBack(3)
    // List is now [1, 2, 3]
    fmt.Println(e1.Value, e2.Value, e3.Value)
    l.Remove(e2)
    // Now list is [1, 3], it does not contain e2
}

на listTest() мы строим список с 3 элементами, и мы храним 2-й элемент в глобальной переменной e2. Затем мы удаляем этот элемент. Теперь мы ожидаем, что за исключением e2 (и обернутое значение в нем) все остальное получает мусор, собранный, когда listTest() возвращает, потому что список недоступен за пределами


сборщик мусора Golang основан на алгоритме трехцветной метки и развертки. Короче говоря, каждая память, которую вы используете в программе, связана с цветом. Цвет определяет, должна ли память быть одета или нет.

этот алгоритм будет помечать память, которая должна быть освобождена, если эта память не упоминается где-то (прямо и косвенно). Но если мы посмотрим на код:

e.prev.next = e.next
e.next.prev = e.prev

эта копия указателя в e.далее в e.прэв.следующий. Теперь, предположим, вы хотите обновить е.пред'.далее новый полностью созданный элемент.

ранее удаленный элемент не будет garbaged, потому что он по-прежнему ссылается на e.следующий.

вот почему эти линии существуют:

e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks

это предотвращает от выходить старые ссылки, и таким образом предотвращает от утечек памяти.