preface

Hello, everyone, I’m Asong. Today, my girlfriend asked me, Komatsu, do you know whether parameter passing in Go language is value passing or reference passing? Oh, ha, I was actually looked down upon, I immediately a operation, to tell him clearly, the little girl, or too tender, we listen to me carefully to ~ ~ ~.

The real participates in the shape parameter

When we define a method using GO, we can define parameters. For example:

func printNumber(args ...int)
Copy the code

Here, args is the parameter. Parameters in the programming language are divided into formal parameters and actual parameters.

Formal arguments: Arguments that are used to define the name and body of a function in order to receive the arguments passed in when the function is called.

Actual parameters: When calling a function with parameters, there is a data transfer relationship between the calling function and the called function. When a function is called from within the calling function, the arguments in parentheses after the function name are called the “actual arguments.”

Here are some examples:

func main(a)  {
 var args int64= 1
 printNumber(args)  // args is the actual parameter
}

func printNumber(args ...int64)  { // Args defined here are formal arguments
	for _,arg := range args{
		fmt.Println(arg) 
	}
}
Copy the code

What is value passing

Value passing, let’s take it literally: what is passed is the value. Passing values means that a function is always passing a copy of the same thing, one copy after another. For example, if we pass an argument of type int, we’re passing a copy of that argument; When you pass a pointer, you pass a copy of the pointer, not the value to which the pointer points. Let’s draw a picture to explain:

What is passing by reference

This should be familiar to those of you who have studied other languages, such as C++, where function arguments are passed by reference. Passing by reference refers to passing the addresses of the actual arguments to the function when it is called, so any changes made to the arguments in the function will affect the actual arguments.

Golang is a value pass

Let’s write a simple example to verify:

func main(a)  {
 var args int64= 1
 modifiedNumber(args) // args is the actual parameter
 fmt.Printf("Actual parameter address %p\n", &args)
 fmt.Printf("The changed value is %d\n",args)
}

func modifiedNumber(args int64)  { // Args defined here are formal arguments
	fmt.Printf("Parameter address %p \n",&args)
	args = 10
}
Copy the code

Running result:

Parameter address0xc0000b4010The address of the actual parameter0xc0000b4008The changed value is1
Copy the code

Let’s write an example to verify that go is only value passing:

func main(a)  {
 var args int64= 1
 addr := &args
 fmt.Printf("The memory address of the original pointer is %p\n", addr)
 fmt.Printf("Addr = address %p\n", &addr)
 modifiedNumber(addr) // args is the actual parameter
 fmt.Printf("The changed value is %d\n",args)
}

func modifiedNumber(addr *int64)  { // Args defined here are formal arguments
	fmt.Printf("Parameter address %p \n",&addr)
	*addr = 10
}
Copy the code

Running result:

The memory address of the original pointer is0xc0000b4008The address of the pointer variable addr0xc0000ae018Parameter address0xc0000ae028The changed value is10
Copy the code

So as we can see from the output, this is a copy of the pointer, because the memory addresses where the two Pointers are stored are different, and even though the Pointers have the same value, they are two different Pointers.

From the above diagram, we can understand it better. We declare a variable args with a value of 1, and its memory address is 0xC0000B4008. From this address, we can find the variable args, which is the address of the pointer to args, addr. Addr is also a pointer variable. It also needs memory to store it. What is its memory address? Is 0 xc0000ae018. When we pass the pointer variable addr to modifiedNumber, it is a copy of the pointer variable, so the new copy of the pointer variable addr, whose memory address has changed, is the new 0xC0000AE028. So, whether it’s 0xC0000AE018 or 0xC0000AE028, we can call it a pointer to the same pointer 0xc0000B4008, which in turn points to the variable args. That’s why we can change the value of args.

From the above analysis, we can confirm that go is passed by value, because the memory address printed in modifieNumber has changed, so it is not passed by reference. Why can chan, Map, slice change the values in go? Don’t worry. Let’s check it out one by one.

sliceIs it also value passing?

Let’s start with a piece of code:

func main(a)  {
 var args =  []int64{1.2.3}
 fmt.Printf("Slice args' address: %p\n",args)
 modifiedNumber(args)
 fmt.Println(args)
}

func modifiedNumber(args []int64)  {
	fmt.Printf("Address of parameter slice %p \n",args)
	args[0] = 10
}
Copy the code

Running result:

Slice args address:0xc0000b8000Parameter slice address0xc0000b8000 
[10 2 3]
Copy the code

Wow, what’s going on, the speed of light in the face, how can all these addresses be the same? And the value has been changed? What’s going on? How do you explain it? You cheat on a woman’s feelings, cheat on my feelings… Sorry about the wrong set. So let’s keep going. We print out the slice address instead of using the & symbol. Let’s add a line of code to test:

func main(a)  {
 var args =  []int64{1.2.3}
 fmt.Printf("Slice args' address: %p \n",args)
 fmt.Printf("Slice args first element address: %p \n",&args[0])
 fmt.Printf("Directly address slice args %v \n",&args)
 modifiedNumber(args)
 fmt.Println(args)
}

func modifiedNumber(args []int64)  {
	fmt.Printf("Address of parameter slice %p \n",args)
	fmt.Printf("The address of the first element in parameter slice args: %p \n",&args[0])
	fmt.Printf("Address %v \n directly to parameter slice args",&args)
	args[0] = 10
}
Copy the code

Running result:

Slice args address:0xc000016140Slice args to the address of the first element:0xc000016140Directly address slice Args &[1 2 3The address of the slice of the parameter0xc000016140Parameter slice the address of the first element in args:0xc000016140Take the address of parameter slice args directly &[1 2 3] 
[10 2 3]
Copy the code

From this example we can see that the address of slice using the & operator is invalid, and the memory address printed using %p is the same as the address of the first element of slice. So why is this? What if the fmt.Printf function did something special? Let’s look at the source code:

FMT package,print.goSo the printValue method in, we cut off the important part, because`slice`It's also a reference type, so it goes into this`case`:case reflect.Ptr:
		// pointer to array or slice or struct? ok at top level
		// but not embedded (avoid loops)
		if depth == 0&& f.Pointer() ! =0 {
			switch a := f.Elem(); a.Kind() {
			case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
				p.buf.writeByte('&')
				p.printValue(a, verb, depth+1)
				return}}fallthrough
	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
		p.fmtPointer(f, verb)
Copy the code

This line of code p.Buuf. WriteByte (‘&’) is why we use & to print the address output with the speech preceded by an &. If we print a slice, we will call p.printValue(a, verb, depth+1) to recursively retrieve the contents of the slice.

case reflect.Array, reflect.Slice:
// Omit part of the code
} else {
			p.buf.writeByte('[')
			for i := 0; i < f.Len(); i++ {
				if i > 0 {
					p.buf.writeByte(' ')
				}
				p.printValue(f.Index(i), verb, depth+1)
			}
			p.buf.writeByte('] ')}Copy the code

This is why fmt.Printf(” directly address slice args %v \n”,&args) output directly address slice args &[1, 2, 3]. With that out of the way, let’s see that the memory address printed using %p is the same as the address of the first element in slice. In the above source code, there is a line fallthrough, which means that the following fmt.Poniter will also be executed. Let me look at the source code:

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}... Omit part of the code// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0. If the slice is empty but non-nil the return value is non-zero.
 func (v Value) Pointer(a) uintptr {
	// TODO: deprecate
	k := v.kind()
	switch k {
	case Chan, Map, Ptr, UnsafePointer:
		return uintptr(v.pointer())
	case Func:
		ifv.flag&flagMethod ! =0{... Omit part of the codeCopy the code

If v’s Kind is Slice, the returned pointer is to the first. If v’s Kind is Slice, the returned pointer is to the first. Return the address of the first element in the slice structure if it is of type slice. FMT.Printf(” slice args’ address: Printf(” address %p \n”,args) and FMT.Printf(” address %p \n”,args) print the same address, because args is the reference type, so they both return the address of the first element in the slice structure. Why the address of the first element in the two slice structures is the same depends on the slice’s underlying structure.

Let’s look at the slice underlying structure:

//runtime/slice.go
type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
Copy the code

Slice is a structure whose first element is a pointer to the first element of the underlying array. Printf returns the address of the first element in the slice structure when the type is slice. At the end of the day, it’s back to pointer handling, except that the pointer is the memory address of the first element in Slice.

Having said all that, one final summary is why slice is also value passing. The reason that passing a reference type can modify the data of the original content is that the underlying default is to pass a pointer to the reference type, but it is also a copy of the pointer, which is still passed by value. So slice passes a copy of the pointer to the first element, causing confusion because of fmt.printf.

Is map also value passing?

Map has the same confusing behavior as Slice. Map we can modify its contents with methods, and it has no obvious pointer. Take this example:

func main(a)  {
	persons:=make(map[string]int)
	persons["asong"] =8

	addr:=&persons

	fmt.Printf("The memory address of the original map is: %p\n",addr)
	modifiedAge(persons)
	fmt.Println("The map value has been changed to :",persons)
}

func modifiedAge(person map[string]int)  {
	fmt.Printf("Function received map from memory address: %p\n",&person)
	person["asong"] =9
}
Copy the code

Take a look at the result:

The originalmapThe memory address is:0xc00000e028Function receivedmapThe memory address is:0xc00000e038
mapThe new value is:map[asong:9]
Copy the code

First meow, oops, the real parameter address is not the same, should be value passed, and so on… How did the map value change? Looking puzzled…

To solve our doubts, let’s start with the source code and take a look at what works:

//src/runtime/map.go
// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h ! = nil, the map can be created directly in h.
// If h.buckets ! = nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
	mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
	if overflow || mem > maxAlloc {
		hint = 0
	}

	// initialize Hmap
	if h == nil {
		h = new(hmap)
	}
	h.hash0 = fastrand()
Copy the code

*hmap *hmap *hmap *hmap Going back to the example above, our func modifiedAge(Person map[string]int) function, which is actually equal to func modifiedAge(person *hmap), is actually passing a copy of the pointer as an argument. Belongs to value passing. In this case, Go eliminates pointer manipulation by wrapping the make function, which is a literal, and makes it easier to use map. The map here can be understood as a reference type, but remember that reference types are not pass-references.

Is chan a value pass?

As usual, here’s an example:

func main(a)  {
	p:=make(chan bool)
	fmt.Printf("The original chan memory address is: %p\n",&p)
	go func(p chan bool){
		fmt.Printf("Chan received from memory address: %p\n",&p)
		// Simulation time
		time.Sleep(2*time.Second)
		p<-true
	}(p)

	select {
	case l := <- p:
		fmt.Println(l)
	}
}
Copy the code

Take another look at the result:

The originalchanThe memory address is:0xc00000e028Function receivedchanThe memory address is:0xc00000e038
true
Copy the code

So what’s going on here? The real parameter address is different, but how does this value get passed back? Hurry up, Tiezi. Let’s analyze Chan as we did map. First look at the source code:

// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
	elem := t.elem

	// compiler checks this but be safe.
	if elem.size >= 1<<16 {
		throw("makechan: invalid channel element type")}ifhchanSize%maxAlign ! =0 || elem.align > maxAlign {
		throw("makechan: bad alignment")
	}

	mem, overflow := math.MulUintptr(elem.size, uintptr(size))
	if overflow || mem > maxAlloc-hchanSize || size < 0 {
		panic(plainError("makechan: size out of range"))}Copy the code

Make returns a pointer to hchan *hchan. Fun (p chan bool) is the same as fun (p *hchan). It is the same as fun (p *hchan). It is the same as fun (p *hchan).

Is that where you basically know that go is a value passed? So there’s one last thing that’s not tested, and that’s struct, so let’s finally verify struct.

structIt’s just value passing

Yes, I’ll give you the answer first. Struct is value passing. Look at this example:

func main(a)  {
	per := Person{
		Name: "asong",
		Age: int64(8),
	}
	fmt.Printf("Original struct address: %p\n",&per)
	modifiedAge(per)
	fmt.Println(per)
}

func modifiedAge(per Person)  {
	fmt.Printf(Struct (struct address: %p\n),&per)
	per.Age = 10
}
Copy the code

We noticed that the Person type, which we defined ourselves, was also passed as a value, but its value (the Age field) had not been changed. We wanted to change it to 10, but the result was 8.

Above summarizes

Go is pass-by-value. We can confirm that all parameters in the GO language are pass-by-value (pass-by-value). Because the copied content is sometimes of non-reference types (int, string, struct, etc.), there is no way to modify the original content data in the function. There are reference types (pointer, map, slice, chan, etc.) that allow you to modify the original content data.

Whether the original content data can be modified is not necessarily related to the passing of values and references. In C++, passing a reference can definitely change the content data. In Go, we only pass values, but we can also change the content data because the parameter is a reference type.

You are thinking of reference type and pass reference as the same concept. They are two concepts, remember!!

Let me give you a test

Feel free to leave your answers in the comments section

Since you all know that Golang only value pass, so this code to help me analyze, here the value can be modified successfully, why use append does not occur expansion?

func main(a) {
	array := []int{7.8.9}
	fmt.Printf("main ap brfore: len: %d cap:%d data:%+v\n".len(array), cap(array), array)
	ap(array)
	fmt.Printf("main ap after: len: %d cap:%d data:%+v\n".len(array), cap(array), array)
}

func ap(array []int) {
	fmt.Printf("ap brfore: len: %d cap:%d data:%+v\n".len(array), cap(array), array)
  array[0] = 1
	array = append(array, 10)
	fmt.Printf("ap after: len: %d cap:%d data:%+v\n".len(array), cap(array), array)
}

Copy the code

Recommended previous articles:

  • The machinery- Go asynchronous task queue

  • Hand taught my sister to write message queue

  • Often meet the exam cache avalanche, cache penetration, cache breakdown

  • Detailed Context package, read this article is enough!!

  • Go -ElasticSearch

  • Interviewer: Have you ever used “for-range” in go? Can you explain the reasons for these questions

  • Learning to wire dependency injection and cron scheduled tasks is as simple as that!

  • I heard you don’t know JWT and Swagger yet. – I’m not eating dinner and HERE I am with a practical project

  • Master these Go features, and you’ll be on your way up to the next level.