go语言杂谈-----函数返回局部变量问题(“逃逸分析”)

多态蝉翼
• 阅读 5958

go语言杂谈-----函数返回局部变量问题(“逃逸分析”)

    在说“逃逸分析”之前,先来啰嗦一下go语言中指针的基本用法,熟悉的看官可以直接跳过这一部分。

1. 指针

Go语言支持指针,通过在变量名前加&来获取变量的地址。
(1) 指针的简单使用,示例如下:
    year := 2020
    ptr := &year
    // 打印ptr的类型和值
    fmt.Printf("%T %v\n", ptr, ptr) // *int 0xc00000a0c8
    // 通过指针解引用的形式获取year的值
    fmt.Println(*ptr)  // 2020
    // 直接通过变量名获取year的值
    fmt.Println(year)  // 2020

(2) 结构体指针访问结构体字段仍然使用"."点操作符,Go语言中没有"->"操作符。示例如下:

    package main

    import "fmt"

    type User struct {
        name string
        age  int
    }

    func main() {
        user := User{
            name:"lioney",
            age:18,
        }
        ptr := &user
        fmt.Println(ptr.name, ptr.age)  // lioney 18
    }

(3) Go语言不支持指针的运算

Go语言由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在C和C++里面指针运算很容易出现问题,因此Go直接在语言层面禁止指针运算。代码如下:
    year := 2020
    ptr := &year
    ptr ++  // invalid operation: ptr++ (non-numeric type *int)

2. Go语言返回函数内的局部变量

(1)返回函数内局部变量的值:

  • 在C/C++语言中,局部变量分配在栈空间,因为函数返回后,系统会自动回收函数里定义的局部变量,所以在返回局部变量的值时,实际是返回局部变量的副本。
  • 在Go语言中返回局部变量的值也是一样的,返回的也是局部变量的副本。代码如下:
    package main

    import "fmt"

    func foo() int {    //int类型函数
        tmp := 1
        fmt.Println(&tmp)  // 0xc00000a0e0
        return tmp      //返回局部变量
    }

    func main() {
        v := foo()
        fmt.Println(&v)  // 0xc00000a0c8(和tmp地址不同)
    }

(2)返回函数内局部变量的地址

  • 在C/C++语言中,操作函数返回后的局部变量的地址,一定会发生空指针异常。要解决这种问题,只需将内存空间分配在堆中即可。
  • 但在Go语言中,函数内部局部变量,无论是动态new出来的变量还是创建的局部变量,它被分配在堆还是栈,是由编译器做“逃逸分析”之后做出的决定。
  • 关于Go语言的“逃逸分析”,可以参考go FAQ里的讲解,大意如下:

    • Go编译器在给函数中的局部变量分配空间时会尽可能地分配在栈空间中,但是如果编译器无法证明函数返回后是否还有该变量的引用存在,则编译器为避免悬挂空指针的错误,就会将该局部变量分配在堆空间中;
    • 如果局部变量占用内存很大,Go编译器会认为将其存储在堆空间中更有意义;
    • Go编译器如果看到了程序中有使用某个变量的地址,则该变量会变成在堆空间上分配内存的候选对象,此时Go编译器会通过分析,判断出该指针的使用会不会超过函数的范围,如果没超过,该变量就可以驻留在栈空间上;如果超过了,就必须将其分配在堆空间中。
对于Go语言中的“逃逸分析”,我们可以看下面的代码:
    package main

    import "fmt"

    func foo() *int {    // 返回int类型指针
        tmp := 2020
        return &tmp      // 返回局部变量tmp的地址
    }

    func main() {
        var ptr *int
        // main函数中引用了foo函数内的局部变量tmp
        // 根据“逃逸分析”,编译器会将其分配在堆空间上
        ptr = foo()
        // foo函数执行结束后tmp不会被释放
        fmt.Println(*p) // 结果为2020,不会报错
    }
  • 但是,无论局部变量分配在堆空间还是栈空间上,我们都不用担心内存泄漏问题,因为Go语言有强大的垃圾回收机制。Go语言声称这样可以释放程序员关于内存的使用限制,更多的让程序员关注于程序功能逻辑本身。
  • 同时,我们也应该知道,变量保存的位置会影响程序的运行效率,在某些场景下,我们需要考虑编译器的“逃逸分析”可能产生的影响。

参考文献

  1. 博客https://blog.csdn.net/li_1013...
  2. 李文塔《Go语言核心编程》第一章相关内容

go语言杂谈-----函数返回局部变量问题(“逃逸分析”)

我是lioney,年轻的后端攻城狮一枚,爱钻研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流后端各种问题!
点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
东方客主 东方客主
5年前
go-map源码简单分析(map遍历为什么时随机的)
GO中map的底层是如何实现的首先Go语言采用的是哈希查找表,并且使用链表解决哈希冲突。GO的内存模型先看这一张map原理图(https://imghelloworld.osscnbeijing.aliyuncs.com/49dfa7b81e19fbab143ddc0a7b3b7fa0.png)map再来看
梦
5年前
微信小程序new Date()转换时间异常问题
微信小程序苹果手机页面上显示时间异常,安卓机正常问题image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/b691e1230e2f15efbd81fe11ef734d4f.png)错误代码vardate'2021030617:00:00'vardateT
易娃 易娃
4年前
Go VS Java:一位资深程序员对两种语言的解读
导读:对于软件开发的编程语言,其实没有万能灵药。本文作者详细介绍了他使用Java和Go这两种编程语言,一个是传统语言,一个是新兴语言的工作方式。image(https://imghelloworld.osscnbeijing.aliyuncs.com/imgs/0f0509de2420894d6c75e8678081e0cd.png)
Easter79 Easter79
4年前
SpringBoot整合Redis乱码原因及解决方案
问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa
Wesley13 Wesley13
4年前
初探 Objective
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言异常处理是许多高级语言都具有的特性,它可以直接中断当前函数并将控制权转交给能够处理异常的函数。不同语言在异常处理的实现上各不相同,本文主要来分析一下ObjectiveC和C这两个语言。为什么要把ObjectiveC和
小万哥 小万哥
2年前
Go 语言学习指南:变量、循环、函数、数据类型、Web 框架等全面解析
学习基础知识掌握Go语言的常见概念,如变量、循环、条件语句、函数、数据类型等等。深入了解Go基础知识的好起点是查阅Go官方文档文章链接:基本语法了解Go语言的基本语法,包括Go程序的执行方式、包引入、主函数等Go中的变量变量是赋予内存位置的名称,用于存储特