for 循环中的隐式内存别名

问题描述 投票:0回答:2

我正在使用 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 文档中找不到任何错误描述。我不明白这个错误。

go static-analysis
2个回答
90
投票

Go 1.22(2024 年 2 月)

这个问题在 Go 1.22 中消失了,因为循环变量没有被重用。来自发行说明草案

在 Go 1.22 中,循环的每次迭代都会创建新变量,以避免意外共享错误。

旧版本的 gosec — 以及使用 gosec 的工具(例如 golangci-lint) — 可能仍会在此处报告问题。如果您的

go.mod
文件声明 Go 版本为 1.22 或更高版本,则这可能会成为误报。运行单元测试以确保。无论如何,要从 gosec 的分析中排除特定的代码行,请在其后面添加注释:

// #nosec G601

或使用 golangci-lint 的配置排除它。

以前的版本

简而言之,该警告意味着您正在获取循环变量的地址。

发生这种情况是因为在

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)
}

如果您在循环内按顺序取消引用,并且您确定没有任何内容泄漏指针,则您可能会忽略此检查。然而,linter 的工作正是报告可能导致问题的代码模式,因此无论如何修复它都是一个好主意。


49
投票

索引将解决问题:

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

}
© www.soinside.com 2019 - 2024. All rights reserved.