转自:http://tonybai.com/2015/01/13/a-hole-about-variable-scope-in-golang/

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

//testpointer.go
package main import (
"fmt"
) var p *int func foo() (*int, error) {
var i int =
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()
}

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

一个有关Golang变量作用域的坑的更多相关文章

  1. golang变量作用域问题-避免使用全局变量

    最近遇到了一个变量作用域的问题,一个比较低级的问题,可能作为一个熟手不应该犯这样的低级错误,但是golang的语法特点可能让你稍微不注意就踩坑,嘿嘿. 变量作用域 全局变量的作用域是整个包,局部变量的 ...

  2. Python3中变量作用域nonlocal的总结

    最近,在工作中踩到了一个关于Python3中nonlocal语句指定的变量作用域的坑.今天趁周六休息总结记录一下. 众所周知,Python中最常见的作用域定义如下:   但是,为了更加方便地在闭包函数 ...

  3. JavaScript函数定义和调用 变量作用域

     本文是笔者在看廖雪峰老师JavaScript教程时的个人总结   JavaScript中函数定义可以是这样的格式 function 函数名(参数) {     函数体 } 也可以是这样的格式     ...

  4. python的变量作用域问题

    偶然掉进了一个坑里.仔细分析了下原因.原来是变量作用域的问题.简单抽象如下: id=1 #许多行代码 [id for id in range(10)] #许多行代码 if id!=1: #做一些事情 ...

  5. Golang开发者常见的坑

    Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...

  6. 【SQL】小心在循环中声明变量——浅析SQL变量作用域

    本文适用:T-SQL(SQL Server) 先看这个语句: --跑3圈 BEGIN --每圈都定义一个表变量,并插入一行 DECLARE @t TABLE(Col INT PRIMARY KEY) ...

  7. golang变量

    一.变量的概念 变量是程序的基本组成单位.变量表示内存中的一个存储区域,该区域有自己的名称(变量名)和类型(数据类型).变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个房间的门 牌号,通 ...

  8. python 变量作用域、闭包

    先看一个问题: 下面代码输出的结果是0,换句话说,这个fucn2虽然已经用global声明了variable1,但还是没有改变变量的值 def func1(): variable1=0 def fun ...

  9. Python-变量、变量作用域、垃圾回收机制原理-global nonlocal

    变量实现原理决定了Python使用的垃圾回收机制为变量引用计数,当这个对象引用计数为0时候,则会自动执行__del__函数回收资源, del方法只是把变量指向的对象引用计数减一而已并删除这个变量 表达 ...

随机推荐

  1. 洛谷P3943 星空

    洛谷P3943 星空 题目背景 命运偷走如果只留下结果, 时间偷走初衷只留下了苦衷. 你来过,然后你走后,只留下星空. 题目描述 逃不掉的那一天还是来了,小 F 看着夜空发呆. 天上空荡荡的,没有一颗 ...

  2. Java语言实现简单FTP软件------>连接管理模块的实现:主机与服务器之间的连接与关闭操作(八)

    (1)FTP连接 运行FTP客户端后,首先是连接FTP服务器,需要输入FTP服务器的IP地址及用户名.密码以及端口号后点击连接按钮开始连接FTP服务器,连接流程图如下图所示. 点击"连接&q ...

  3. docker centos yum 源

    aliyun yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.rep ...

  4. 通过systemd配置Docker

    1. systemd Service相关目录 通常情况下,我们有3种方式可以配置etcd中的service.以docker为例,1)在目录/etc/systemd/system/docker.serv ...

  5. Vue-cli创建项目从单页面到多页面4 - 本地开发服务器设置代理

    前后端分离开发时,有时候会遇到跨域的情况:只在开发的时候存在跨域,项目上线后,由于配置的域名相同,跨域就会不存在. 这个时候,有两种方案可以比较快的解决: 1.利用h5的特性,使用cors,在ngni ...

  6. 第五篇、javascript正则表达式二

    一.内容概要 1)创建着呢规则表达式对象的两种方法 2)正则表达式的常用属性和方法 3)string对象常用方法中可以使用正则表达式 4)ES中其他预定义的对象:Math.Date.Number.Bo ...

  7. Docker学习(二)

    docker run ubuntu /bin/echo 'Hello world' docker run -t -i ubuntu /bin/bash docker run  命令运行一个容器 -t ...

  8. Java -- 表达式类型的自动提升

    1. 提升规则: a. 所有byte型,short型和char型将被提升到int型. b. 整个算数表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型. 例1: short val = 5; ...

  9. 动态规划 最长回文子串 leetcode5

    public static String longestPalindrome(String s) { if(null==s||s.length()==0) return s; int n=s.leng ...

  10. hdu5606 tree (并查集)

    tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submis ...