Неявное псевдоним памяти в цикле for

Я использую golangci-lint и получаю сообщение об ошибке в следующем коде:

versions []ObjectDescription
... (populate versions) ...

for i, v := range versions {
    res := createWorkerFor(&v)
    ...

}

ошибка:

G601: Implicit memory aliasing in for loop. (gosec)
                     res := createWorkerFor(&v)
                                            ^

Что именно означает «неявное псевдоним памяти в цикле for»? Я не смог найти описание ошибки в документации golangci-lint. Я не понимаю эту ошибку.


person Dean    schedule 18.06.2020    source источник
comment
Это относится к тому факту, что получение адреса v в цикле опасно (в зависимости от того, что вы с ним делаете, но его сохранение и асинхронное использование сломают ситуацию). См. эту запись распространенные ошибки.   -  person Marc    schedule 18.06.2020
comment
В вашем цикле for используется только одна переменная v, а значение v обновляется циклом for. Но так как это всего лишь одна переменная, то у нее всего один адрес: &v является константой и не изменяется во время итерации цикла. Это типичная проблема в параллельном коде, так как все созданные рабочие процессы будут работать с одним и тем же местом в памяти и, следовательно, либо с одним и тем же значением, либо со значением, которое является колоритным. См. golang.org/doc/faq#closures_and_goroutines.   -  person Volker    schedule 18.06.2020
comment
В моем случае, основываясь на приведенной выше информации, я решил просто удалить &, что может выделить немного больше памяти, но объект, который я передал, был в основном небольшой частью запроса БД (данные сервера, такие как имя хоста, IP, ОС). и т. д.) ... поэтому, удалив амперсанд, я передал копию методу, обрабатывающему данные, вместо ссылки.   -  person TommyTheKid    schedule 18.03.2021


Ответы (2)


Индексация решит проблему:

for i := range versions {
    res := createWorkerFor(&versions[i])
    ...

}
person Xesina    schedule 20.06.2020

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

Это происходит потому, что в for операторах повторно используются переменные итерации. На каждой итерации значение следующего элемента в выражении диапазона присваивается переменной итерации; v не меняется, меняется только его значение. Следовательно, выражение &v относится к одному и тому же месту в памяти.

Следующий код печатает один и тот же адрес памяти четыре раза:

for _, n := range []int{1, 2, 3, 4} {
    fmt.Printf("%p\n", &n)
}

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

Распространенные способы предотвращения проблемы:

  • проиндексировать ранжированный срез/массив/карту. Это берет адрес фактического элемента в i-й позиции вместо переменной итерации
for i := range versions {
    res := createWorkerFor(&versions[i])
}
  • переназначить переменную итерации внутри цикла
for _, v := range versions {
    v := v
    res := createWorkerFor(&v) // this is now the address of the inner v
}
  • с замыканиями, передайте переменную итерации в качестве аргумента замыканию
for _, v := range versions { 
    go func(arg ObjectDescription) {
        x := &arg // safe
    }(v)
}
person blackgreen    schedule 04.07.2021