一个有关Golang变量作用域的坑

bigwhite · 2015-01-20 09:24:46 · 43274 次点击 · 预计阅读时间 2 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2015-01-20 09:24:46 的文章,其中的信息可能已经有所发展或是发生改变。

临近下班前编写和调试一段Golang代码,但运行结果始终与期望不符,怪异的很,下班前依旧无果。代码Demo如下:

//testpointer.go
package main

import (
        "fmt"
)

var p *int

func foo() (*int, error) {
        var i int = 5
        return &i, nil
}

func bar() {
        //use p
        fmt.Println(*p)
}

func main() {
        p, err := foo()
        if err != nil {
                fmt.Println(err)
                return
        }
        bar()
        fmt.Println(*p)
}

这段代码原意是定义一个包内全局变量p,用foo()的返回值对p进行初始化,在bar中使用p。预期结果:bar()和main()中均输出5。但编译执行后的结果却是:

$go run testpointer.go
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x20d1]

goroutine 1 [running]:
main.bar()
    /Users/tony/Test/Go/testpointer.go:17 +0xd1
main.main()
    /Users/tony/Test/Go/testpointer.go:26 +0x11c

goroutine 2 [runnable]:
runtime.forcegchelper()
    /usr/local/go/src/runtime/proc.go:90
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2232 +0×1

goroutine 3 [runnable]:
runtime.bgsweep()
    /usr/local/go/src/runtime/mgc0.go:82
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2232 +0×1

goroutine 4 [runnable]:
runtime.runfinq()
    /usr/local/go/src/runtime/malloc.go:712
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:2232 +0×1
exit status 2

晚饭后,继续调试这段代码。怎么还crash了!代码看似半点问题都没有,难道是Go编译器的问题,我用的可是最新的1.4,切换回1.3.3,问题依旧啊。看来还是代码的问题,但问题在哪里呢?加上些打印语句再看看:

func bar() {
        //use p
        fmt.Printf("%p, %T\n", p, p) //output:
0x14dc80, 0×0, *int
        fmt.Println(*p) //Crash!!!
}

func main() {
        fmt.Printf("%p, %T\n", p, p) //output: 0x14dc80, 0×0, *int
        p, err := foo()
        if err != nil {
                fmt.Println(err)
                return
        }
        fmt.Printf("%p, %T\n", p, p) //output: 0x2081c6020, 0x20818a258, *int
        bar()
        fmt.Println(*p)
}

通过打印输出,发现从foo函数中返回的p(0x2081c6020)与全局变量的p(0x14dc80)居然不是一个地址,也就是说不是一个变量。而且 从bar()中的调试输出来看,全局变量p在foo函数返回时并未被赋值为foo中变量i的地址,而依然是一个nil值,从而导致程序Crash。

好了,废话不说了,该是揭晓真相的时候了。问题就在于":="。在main这个作用域中,我们使用了

p, err := foo()

最初的理解是golang会定义新变量err,p为初始定义的那个全局变量。但实际情况是,对于使用:=定义的变量,如果新变量p与那个同名已定义变量 (这里就是那个全局变量p)不在一个作用域中时,那么golang会新定义这个变量p,遮盖住全局变量p,这就是导致这个问题的真凶。

我们将main函数改为:

func main() {
        var err error
        p, err = foo()
        if err != nil {
                fmt.Println(err)
                return
        }
        bar()
}

则执行结果就完全符合预期了。

© 2015, bigwhite. 版权所有.


有疑问加站长微信联系(非本文作者)

本文来自:Tony Bai

感谢作者:bigwhite

查看原文:一个有关Golang变量作用域的坑

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

43274 次点击  ∙  2 赞  
加入收藏 微博
9 回复  |  直到 2019-01-28 11:37:32
duanquanyong
duanquanyong · #1 · 10年之前

p, err := foo() 看到这里就明白了- -

thenew
thenew · #2 · 10年之前
duanquanyongduanquanyong #1 回复

p, err := foo() 看到这里就明白了- -

真是坑啊,大型工程看别人代码稍不注意可不被坑死

muziwen1992
muziwen1992 · #3 · 9年之前

给楼主点个赞

3zheng
3zheng · #4 · 8年之前

foo()函数里的i变量不会被回收吗,从C转过来感觉这个指针是个野指针,有高手解惑吗

lz120792
lz120792 · #5 · 8年之前
3zheng3zheng #4 回复

foo()函数里的i变量不会被回收吗,从C转过来感觉这个指针是个野指针,有高手解惑吗

gl5773477
gl5773477 · #6 · 8年之前

此文过时了

gl5773477
gl5773477 · #7 · 8年之前

而且,我觉得输出很正常,没有任何毛病,foo返回新的地址值给p,p当然改变了,而非所谓函数作用域内:=定义新的变量覆盖全局p

gl5773477
gl5773477 · #8 · 8年之前

额,看差了~~err这个完全可以去掉,没什么意义

theDreamBear
theDreamBear · #9 · 6年之前

golang 有自己的垃圾回收机制,这和c++ 不一样, go的变量编译器会根据情况放到 堆还是栈中, 这个函数内的i 会被放到堆中

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传