面试官问:Go 中的参数传递是值传递还是引用传递?

一个程序中,变量分为变量名和变量内容,变量内容的存储一般会被分配到堆和栈上。而在 Go 语言中有两种传递变量的方式值传递和引用传递。其中值传递会直接将变量内容附在变量名上传递,而引用传递会将变量内容的地址附在变量名上传递。
Golang 中是如何做到
如果在面试时有面试官提问你:“Go 的参数是如何传递的?”你会怎么回答呢?
这个问题其实只有一个答案。因为在 Golang 中所有的类型传递都是通过值传递实现的,而不是引用传递,即使是指针的传递也是通过 copy 指针的方式进行。另外对于一些包裹了底层数据的数据结构,其值传递的过程中,复制的也只是实例的指针,而不是底层数据所暴露出来的指针。
下面以 Go 版本 1.8 的 slice 为例来简单了解一下:
func makeslice(et *_type, len, cap int) unsafe.Pointer {mem, overflow := math.MulUintptr(et.size, uintptr(cap))if overflow || mem > maxAlloc || len < 0 || len > cap {// NOTE: Produce a 'len out of range' error instead of a// 'cap out of range' error when someone does make([]T, bignumber).// 'cap out of range' is true too, but since the cap is only being// supplied implicitly, saying len is clearer.// See golang.org/issue/4085.mem, overflow := math.MulUintptr(et.size, uintptr(len))if overflow || mem > maxAlloc || len < 0 {panicmakeslicelen()}panicmakeslicecap()}return mallocgc(mem, et, true) // 申请内存}
可以看到 slice 在初始化的过程中调用了 runtime 中的 makeslice 函数,这个函数会将 slice 的地址返回给接受的变量。
type slice struct {array unsafe.Pointer // 底层数组的地址len intcap int}// 初始化过程p := make([]int,0)fmt.Printf("变量p的地址%p", &p)fmt.Printf("slice的地址%p\n", p)
上面打印时出现的是的内容,这是因为 Go 内部实现了自动解引用(即 Go 内部实现的解引用操作)。 自动解引用时 receive 会从指针类型转变为值类型。顺带一提自动取引用时 receiver 会从值类型转变为指针类型。

如果未实现自动解引用时会怎样呢?下面是未实现自动解引用的情况:
// 当我们打印变量p的时候,实际过程是发生了这样的变化// 只是猜测,当然发生解引用是一定的// & 取地址操作符// * 根据地址取值操作 也称之为解引用运算法,间址运算符// 1. 获取指针地址 &p// 2. 获取array的地址 &((&p).array)// 3. 获取底层数组实际内容 *&((&p).array)
未实现自动借用的函数传递过程,也是通过复制指针的方式来传递的,内容如下:

package mainimport ("fmt")func change(p1 []int) {fmt.Printf("p1的内存地址是: %p\n", &p1) // p1的内存地址是: 0xc0000a6048fmt.Printf("函数里接收到slice的内存地址是:%p\n", p1) // 函数里接收到slice的内存地址是:0xc00008c030p1 = append(p1, 30)}func main() {p := make([]int, 3) // 抛出一个指针p = append(p, 20)fmt.Printf("p的内存地址是: %p\n", &p) // p的内存地址是: 0xc00009a018fmt.Printf("slice的内存地址是:%p\n", p) // slice的内存地址是:0xc00008c030change(p) // 重新生成一份地址p1 指向slice地址fmt.Printf("修改之后p的内存地址 %p\n", &p) // 修改之后p的内存地址 0xc00009a018fmt.Printf("修改之后slice的内存地址 %p\n", p) // 修改之后slice的内存地址 0xc00008c030fmt.Println("修改之后的slice:", p) // 修改之后的slice [0 0 0 20]fmt.Println(*&p) // [0 0 0 20]}
需要注意的是,在函数传递的过程中 copy 的不是 slice 内部指向底层数组的指针,而是在 makeslice 函数所返回的指针。
源码实现
大家在看一些老旧的文章的时候,可能看到过这样的说法:make 返回的是 slice 的实例。但其实这种说法已经过时了,在 Golang 1.2 版本之后 make 返回的就是实例的指针。
github pr 地址:https://github.com/golang/go/commits/dev.boringcrypto.go1.12/src/runtime/slice.go


扩展
其实和 slice 类似的还有 map,chan。
先说 map,map 的官网定义:“Go provides a built-in map type that implements a hash table.Map types are reference types, like pointers or slices.”而 chan 和 map 一样也是一个指针,也就是说二者和 slice 的原理相似。
func makemap(t *maptype, hint int, h *hmap) *hmap {mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)if overflow || mem > maxAlloc {hint = 0}...}
func makechan(t *chantype, size int) *hchan {...mem, overflow := math.MulUintptr(elem.size, uintptr(size))if overflow || mem > maxAlloc-hchanSize || size < 0 {panic(plainError("makechan: size out of range"))}...}
如果平时的使用中不注意,会出现一些不必要的麻烦,如:
package mainimport "fmt"type InfoIns struct {Name stringinfo []string}func NewInfoIns() InfoIns{return InfoIns{Name: "",info: nil,}}func (n *InfoIns) SetInfo(info []string){n.info = info}func main(){infoIns := NewInfoIns()info := []string{"p1", "p2", "p3"}infoIns.SetInfo(info)info[1] = "p4"fmt.Println(infoIns.info) // [p1 p4 p3]}
这里的InfoIns 在SetInfo之后存的是 info 的地址。一旦 info 在后续有改动 InfoIns 中的内容也随之会被改动。解决的方法是在 SetInfo 的时候重新申请一份地址。
func (n *InfoIns) SetInfo(info []string){n.info = make([]string, len(info))copy(n.info, info)}
脚注
借助 Goland 查看 Go 源码的方式:Ctrl+Shift+f 全局搜索,选择 Scope 中的 ALL Place。

快 来 找 又 小 拍

推 荐 阅 读 


设为星标

更新不错过




设为星标

更新不错过
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/
关注KnowSafe微信公众号随时掌握互联网精彩
- 亚数发布新一代智能证书管理体系TrustAsia CaaS
- XIAOJUSURVEY滴滴开源的一款轻量、安全的调研系统
- UotanToolboxNT一站式刷机工具箱,支持刷入 Recovery、修补 Boot、线刷、自定义刷入
- Dude Suite Web Security Tools 集成化的Web渗透测试工具集
- 连续6年参加进博会,高通公司带来5G+AI引领下的智能未来
- 诸子笔会2022 | 陈圣:小议以色列的网络攻防能力建设
- 美的回应“勒索病毒、千万赎金”:系谣言。
- 新耀东方上安会一日见闻实录
- 警惕!75%Office 365账户遭受恶意接管攻击!
- 测测自己对消费者权益的理解,赢取丰富奖品
- 高朋满座话未来|专访努比亚联合创始人、高级副总裁余航
- 杭州未来科技城•Qualcomm中国•中科创达联合创新中心落成并投入使用



