2

Hello everyone, I am fried fish.

A few days ago in our Go exchange group, a small partner asked "Is xxx a reference type?" This question triggered a discussion for nearly 5 hours:

Going back and forth to the Nikkei problem, almost every month someone has to fight because of this. Is Go language is passed by value (pass by value) or by reference (pass by reference) ?

Go official definition

This section refers to "When are function parameters passed by value?" in Go official FAQ, and the content is as follows.

Like all languages in the C series, everything in the Go language is passed by value. In other words, a function always gets a copy of what is passed, just like an assignment statement assigns a value to a parameter.

E.g:

  • Pass an int value to a function and you will get a copy of the int. Passing a pointer value will get a copy of the pointer, but not the data it points to.
  • Maps and slices behave like pointers: they are descriptors that contain pointers to the underlying map or slice data.

    • Copying a map or slice value does not copy the data it points to.
    • Copying an interface value will copy what is stored in the interface value.
    • If the interface value holds a structure, copying the interface value will copy the structure. If the interface value holds a pointer, copying the interface value will copy the pointer, but it will also not copy the data it points to.

To focus, everything in the Go language is passed by value, there is no pass by reference. Don't just apply other concepts directly, you will make preconceived mistakes.

By value and by reference

Pass by value

Pass by value is also called pass by value. The refers to copy the actual parameters to when calling the function, so that if the parameters are modified in the function, the actual parameters will not be affected.

Simply put, value transfer, what is passed is a copy of the parameter, which is a copy, and cannot be regarded as a thing in essence, and it is not a memory address.

Case one is as follows:

func main() {
    s := "脑子进煎鱼了"
    fmt.Printf("main 内存地址:%p\n", &s)
    hello(&s)
}

func hello(s *string) {
    fmt.Printf("hello 内存地址:%p\n", &s)
}

Output result:

main 内存地址:0xc000116220
hello 内存地址:0xc000132020

We can see that the memory address pointed to by the variable s in the main function is 0xc000116220 . After passing the parameters of the hello function, the internal memory address output is 0xc000132020 , and the two have changed.

Based on this, we can conclude that in the Go language it is indeed all value pass. Is it possible to modify the value in the function without affecting the main function?

Case two is as follows:

func main() {
    s := "脑子进煎鱼了"
    fmt.Printf("main 内存地址:%p\n", &s)
    hello(&s)
    fmt.Println(s)
}

func hello(s *string) {
    fmt.Printf("hello 内存地址:%p\n", &s)
    *s = "煎鱼进脑子了"
}

We modified the value of the variable s in the hello function, so what is the value of the variable s that we output in the main function. Is it "fried fish in the brain" or "fried fish in the brain"?

Output result:

main 内存地址:0xc000010240
hello 内存地址:0xc00000e030
煎鱼进脑子了

The output result is "fried fish into the brain". At this time, everyone may be murmured again. The fried fish clearly stated that the Go language only has value transfer. It also verified that the memory addresses of the two are different. Why does his value change now? This is Why?

Because "if the passed value is an address pointing to the memory space, then this memory space can be modified".

That is, these two memory addresses are actually pointers to pointers, and their roots all point to the same pointer, that is, they point to the variable s. Therefore, we further modify the variable s and get the result of outputting "fried fish in the brain".

Pass by reference

Pass by reference, also known as pass by reference, means that when the function is called, the address of the actual parameter is directly passed to the function , then the modification of the parameter in the function will affect the actual parameter.

In the Go language, the official has made it clear that there is no passing by reference, that is, there is no passing by reference.

Therefore, a simple description is borrowed from the text, as in the example, even if you pass in the parameters, the final output memory address is the same.

The most controversial map and slice

At this time, some friends are puzzled. You can see that the map and slice types in the Go language can be directly modified. Isn't it the same memory address, isn't it a reference?

In fact, there is a very important reminder in the FAQ: "map and slice behave like pointers, they are descriptors that contain pointers to the underlying map or slice data".

map

For the map type, let's expand further to see an example:

func main() {
    m := make(map[string]string)
    m["脑子进煎鱼了"] = "这次一定!"
    fmt.Printf("main 内存地址:%p\n", &m)
    hello(m)

    fmt.Printf("%v", m)
}

func hello(p map[string]string) {
    fmt.Printf("hello 内存地址:%p\n", &p)
    p["脑子进煎鱼了"] = "记得点赞!"
}

Output result:

main 内存地址:0xc00000e028
hello 内存地址:0xc00000e038

It is indeed value passing, what should be the result of the modified map. Since it is value passing, it must be "this time!", right?

Output result:

map[脑子进煎鱼了:记得点赞!]

The result is that the modification is successful, and "Remember to like it!" is output. It's embarrassing now, why is it passed by value, and it can achieve a similar effect as a reference, and can it be modified to the source value?

The tips here are:

func makemap(t *maptype, hint int, h *hmap) *hmap {}

This is the underlying runtime method for creating the map type. Note that it returns a *hmap , which is a pointer. That is, the Go language encapsulates the related methods of the map type to achieve the effect that users need to pay attention to pointer passing.

That is to say, when we call the hello method, it is equivalent to passing in a pointer parameter hello(*hmap) , which is similar to the case 2 of the previous value type.

We call this kind of situation "reference type", but "reference type" is not the same as passing by reference or passing by reference. There is a clear distinction.

Similar to the map type in Go, there is also the chan type:

func makechan(t *chantype, size int) *hchan {}

The same effect.

slice

For the slice type, let's expand further to see an example:

func main() {
    s := []string{"烤鱼", "咸鱼", "摸鱼"}
    fmt.Printf("main 内存地址:%p\n", s)
    hello(s)
    fmt.Println(s)
}

func hello(s []string) {
    fmt.Printf("hello 内存地址:%p\n", s)
    s[0] = "煎鱼"
}

Output result:

main 内存地址:0xc000098180
hello 内存地址:0xc000098180
[煎鱼 咸鱼 摸鱼]

From the results, the memory addresses of the two are the same, and they are successfully changed to the value of the variable s. Isn't this passed by reference, fried fish overturned?

Pay attention to two details:

  • & is not used to get the address.
  • You can use %p to print directly.

The reason why the above two things can be done at the same time is because the standard library fmt optimized for this:

func (p *pp) fmtPointer(value reflect.Value, verb rune) {
    var u uintptr
    switch value.Kind() {
    case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
        u = value.Pointer()
    default:
        p.badVerb(verb)
        return
    }

value.Pointer that the code 0616e50dddaf33, the standard library has carried out special processing, the pointer address of the directly corresponding value, of course, there is no need to take the address symbol.

This is why the standard library fmt can output the value corresponding to the slice type:

func (v Value) Pointer() uintptr {
    ...
    case Slice:
        return (*SliceHeader)(v.ptr).Data
    }
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Data attribute converted internally is exactly the runtime representation of the slice type SliceHeader in the Go language. When we call %p output, we store the address of the array element at the bottom of the output slice.

The next question is: why the slice type can directly modify the value of the source data.

In fact, the principle is the same as the output. When the Go language is running, the pointer to the underlying array of the corresponding slice type is passed, but it needs to be noted that it uses a copy of the pointer. Strictly speaking, it is a reference type, and it is still passed by value.

Is it wonderful?

Summarize

In today's article, we have conducted a basic explanation and analysis on the Nikkei problem of the Go language: "Does the Go language pass by value (pass by value) or by reference (pass by reference)".

In addition, in the industry, the most confusing types are slice, map, chan and other types, which are considered to be "pass by reference", so that xxx in the Go language is passed by reference. We have also demonstrated a case for this.

This is actually a misconception, because: "If the passed value points to an address in the memory space, this memory space can be modified."

It did make a copy, but he also achieved the effect of modifying the source data by various means (in fact, passing pointers), which is a reference type.

Stone hammer, Go language only has value transfer,

If you have any questions please comment and feedback exchange area, best relationship is mutual achievement , everybody thumbs is fried fish maximum power of creation, thanks for the support.

The article is continuously updated, and you can read it on search [My brain is fried fish], this article 1616e50dddb064 GitHub github.com/eddycjy/blog has been included, welcome to Star to remind you. You can add me to WeChat cJY0728 , I will pull you into the Go reader exchange group, and exchange technology with thousands of developers.

refer to


煎鱼
8.4k 声望12.8k 粉丝