5
头图

In a program, variables are divided into variable names and variable contents. The storage of variable contents is generally allocated to the heap and stack. In Go, there are two ways to pass variables, pass by value and pass by reference. Among them, pass-by-value will directly attach the variable content to the variable name, and pass-by-reference will pass the address of the variable content to the variable name.

How to do it in Golang

If an interviewer asks you during an interview: "How are Go parameters passed?" How would you answer?

There is really only one answer to this question. Because all type transfer in Golang is achieved by value transfer , not by reference, even the transfer of pointers is done by copying the pointer. In addition, for some data structures that wrap the underlying data, in the process of passing the value, only the pointer of the instance is copied, not the pointer exposed by the underlying data.

Let's take the slice of Go version 1.8 as an example to briefly understand:

 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) // 申请内存
}

It can be seen that the slice calls the makeslice function in the runtime during the initialization process, and this function returns the address of the slice to the accepted variable.

 type slice struct {
        array unsafe.Pointer // 底层数组的地址
        len   int
        cap   int
}

// 初始化过程    
p := make([]int,0)
fmt.Printf("变量p的地址%p", &p)
fmt.Printf("slice的地址%p\n", p)

What appears when printed above is because Go implements automatic dereferencing internally (that is, the dereferencing operation that Go implements internally). The receive will be converted from a pointer type to a value type during automatic dereferencing. By the way, the receiver will be converted from a value type to a pointer type when the reference is automatically taken.

What if automatic dereferencing is not implemented? Here's what happens when automatic dereferencing is not implemented:

 // 当我们打印变量p的时候,实际过程是发生了这样的变化
// 只是猜测,当然发生解引用是一定的
// & 取地址操作符
// * 根据地址取值操作 也称之为解引用运算法,间址运算符
// 1. 获取指针地址  &p
// 2. 获取array的地址 &((&p).array) 
// 3. 获取底层数组实际内容 *&((&p).array)

The function transfer process that does not implement automatic borrowing is also passed by copying the pointer, and the content is as follows:

 package main

import (
        "fmt"
)

func change(p1 []int) {
        fmt.Printf("p1的内存地址是: %p\n", &p1)  // p1的内存地址是: 0xc0000a6048
        fmt.Printf("函数里接收到slice的内存地址是:%p\n", p1)  // 函数里接收到slice的内存地址是:0xc00008c030

        p1 = append(p1, 30)
}

func main() {
        p := make([]int, 3) // 抛出一个指针
        p = append(p, 20)
        fmt.Printf("p的内存地址是: %p\n", &p) // p的内存地址是: 0xc00009a018
        fmt.Printf("slice的内存地址是:%p\n", p) // slice的内存地址是:0xc00008c030

        change(p) // 重新生成一份地址p1 指向slice地址 
        fmt.Printf("修改之后p的内存地址 %p\n", &p) // 修改之后p的内存地址 0xc00009a018
        fmt.Printf("修改之后slice的内存地址 %p\n", p) // 修改之后slice的内存地址 0xc00008c030
        fmt.Println("修改之后的slice:", p) // 修改之后的slice [0 0 0 20]
        fmt.Println(*&p) // [0 0 0 20]
}

It should be noted that in the process of function passing, what is copied is not the pointer to the underlying array inside the slice, but the pointer returned by the makeslice function.

source code implementation

When you read some old articles, you may have seen this statement: make returns an instance of slice. But in fact, this statement is outdated, after Golang 1.2 version make returns a pointer to the instance.

github pr address: https://github.com/golang/go/commits/dev.boringcrypto.go1.12/src/runtime/slice.go

expand

In fact, there are map, chan similar to slice.

Let's talk about map first. The official website definition of map: "Go provides a built-in map type that implements a hash table. Map types are reference types, like pointers or slices." And chan is also a pointer like map, which means that both Similar to the principle of 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"))
   }
   ...
}

If you do not pay attention to the usual use, there will be some unnecessary troubles, such as:

 package main

import "fmt"

type InfoIns struct {
        Name string
        info []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]
}

The InfoIns here stores the address of info after SetInfo. Once the info is changed in the future, the content in the InfoIns will be changed accordingly. The solution is to re-apply for an address during SetInfo.

 func (n *InfoIns) SetInfo(info []string){
        n.info = make([]string, len(info))
        copy(n.info, info)
}

footnote

The way to view Go source code with Goland: Ctrl+Shift+f Global search, select ALL Place in Scope.

Recommended reading

One article talks about those things about IP addresses

Decoration pattern of common design patterns in Golang


云叔_又拍云
5.9k 声望4.6k 粉丝

又拍云是专注CDN、云存储、小程序开发方案、 短视频开发方案、DDoS高防等产品的国内知名企业级云服务商。