Golang Address of fragments of empty structures

I have a basic question in empty struct , and I'm trying to figure out the following different outputs that I get when I try to get the address of the elements of the backing arrays for two slices:

 a := make([]struct{}, 10) b := make([]struct{}, 20) fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) 

The above snippet returns :

 &a == &b false &a[0] == &b[0] true 

However, given the following slightly modified snippet:

 a := make([]struct{}, 10) b := make([]struct{}, 20) fmt.Println(a[0], &a[0]) fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) 

The above snippet returns :

 {} &{} &a == &b false &a[0] == &b[0] false 

Can someone explain the reason for the above difference? Thanks!

[Follow Up] Make the following changes:

 package main import "fmt" type S struct{} func (s *S) addr() { fmt.Printf("%p\n", s) } func main() { a := make([]S, 10) b := make([]S, 20) fmt.Println(a[0], &a[0]) fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) //a[0].addr() //b[0].addr() } 

Returns the same result:

 {} &{} &a == &b false &a[0] == &b[0] false 

Although, uncommenting the method calls, it returns :

 {} &{} &a == &b false &a[0] == &b[0] true 0x19583c // ==> [depends upon env] 0x19583c // ==> [depends upon env] 
+5
source share
2 answers

Before you go deeper, be aware that according to the specification, the program is correct regardless of whether it gives equal or different addresses for values ​​with zero size, because the specification only states that they can be the same, but they do not require that they be the same.

Spec: size and alignment guarantees:

The type of a structure or array is of zero size if it does not contain fields (or elements, respectively) whose size is greater than zero. Two different zero-sized variables can have the same memory address.

So what you are experiencing is the implementation detail. The decisions taken contain more detailed information and factors, the following explanation is valid and sufficient only for specific examples:

In the first example, the addresses of the base array for your fragments are used only inside the main() function, they do not go into the heap. What you type is the result of comparing addresses. These are just bool values; they do not include address values. Therefore, the compiler prefers to use the same address for the support array a and b .

In your second example, the addresses of auxiliary arrays (more specifically, the addresses of some elements of backup arrays) are used outside the main() function, they are passed and used inside the fmt.Println() function, because you also print these addresses.

We can "prove" this by passing the -gcflags '-m' parameters to the Go tool, asking it to print the result of the escape analysis.

In the first example, saving your code in play.go by running go run -gcflags '-m' play.go , the output is:

 ./play.go:10:14: "&a == &b" escapes to heap ./play.go:10:29: &a == &b escapes to heap ./play.go:11:14: "&a[0] == &b[0]" escapes to heap ./play.go:11:38: &a[0] == &b[0] escapes to heap ./play.go:8:11: main make([]struct {}, 10) does not escape ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:26: main &a does not escape ./play.go:10:32: main &b does not escape ./play.go:10:13: main ... argument does not escape ./play.go:11:32: main &a[0] does not escape ./play.go:11:41: main &b[0] does not escape ./play.go:11:13: main ... argument does not escape &a == &b false &a[0] == &b[0] true 

As we see, the addresses do not disappear.

Running go run -gcflags '-m' play.go with a second example, output:

 ./play.go:10:15: a[0] escapes to heap ./play.go:10:20: &a[0] escapes to heap ./play.go:10:20: &a[0] escapes to heap ./play.go:8:11: make([]struct {}, 10) escapes to heap ./play.go:11:14: "&a == &b" escapes to heap ./play.go:11:29: &a == &b escapes to heap ./play.go:12:14: "&a[0] == &b[0]" escapes to heap ./play.go:12:38: &a[0] == &b[0] escapes to heap ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:13: main ... argument does not escape ./play.go:11:26: main &a does not escape ./play.go:11:32: main &b does not escape ./play.go:11:13: main ... argument does not escape ./play.go:12:32: main &a[0] does not escape ./play.go:12:41: main &b[0] does not escape ./play.go:12:13: main ... argument does not escape {} &{} &a == &b false &a[0] == &b[0] false 

As you can see, a[0] , &a[0] go to the heap, so the support array a dynamically allocated and, therefore, it will have a different address than address b .

Let us prove this further. Modify the second example to have a third variable c , the address of which will also not be printed, and compare b with c :

 a := make([]struct{}, 10) b := make([]struct{}, 20) c := make([]struct{}, 30) fmt.Println(a[0], &a[0]) fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) fmt.Println("&b == &c", &b == &c) fmt.Println("&b[0] == &c[0]", &b[0] == &c[0]) 

Running go run -gcflags '-m' play.go on this output:

 ./play.go:11:15: a[0] escapes to heap ./play.go:11:20: &a[0] escapes to heap ./play.go:11:20: &a[0] escapes to heap ./play.go:8:11: make([]struct {}, 10) escapes to heap ./play.go:12:14: "&a == &b" escapes to heap ./play.go:12:29: &a == &b escapes to heap ./play.go:13:14: "&a[0] == &b[0]" escapes to heap ./play.go:13:38: &a[0] == &b[0] escapes to heap ./play.go:14:14: "&b == &c" escapes to heap ./play.go:14:29: &b == &c escapes to heap ./play.go:15:14: "&b[0] == &c[0]" escapes to heap ./play.go:15:38: &b[0] == &c[0] escapes to heap ./play.go:9:11: main make([]struct {}, 20) does not escape ./play.go:10:11: main make([]struct {}, 30) does not escape ./play.go:11:13: main ... argument does not escape ./play.go:12:26: main &a does not escape ./play.go:12:32: main &b does not escape ./play.go:12:13: main ... argument does not escape ./play.go:13:32: main &a[0] does not escape ./play.go:13:41: main &b[0] does not escape ./play.go:13:13: main ... argument does not escape ./play.go:14:26: main &b does not escape ./play.go:14:32: main &c does not escape ./play.go:14:13: main ... argument does not escape ./play.go:15:32: main &b[0] does not escape ./play.go:15:41: main &c[0] does not escape ./play.go:15:13: main ... argument does not escape {} &{} &a == &b false &a[0] == &b[0] false &b == &c false &b[0] == &c[0] true 

Since only &a[0] printed, but not &b[0] and &c[0] , so &a[0] == &b[0] will be false , but &b[0] == &c[0] will be true .

0
source

The empty structure is basically: almost nothing. This is size 0:

 var s struct{} fmt.Println(unsafe.Sizeof(s)) 

Returns 0 .

Now, why the addresses of two empty structures are sometimes the same and sometimes not, it depends on the internal elements. The only key we can get is the specification :

The type of a structure or array is 0 if it does not contain fields (or elements, respectively) whose size is greater than zero. Two different zero-sized variables can have the same memory address.

Also note the following code, in which the first print has moved to the last:

 fmt.Println("&a == &b", &a == &b) fmt.Println("&a[0] == &b[0]", &a[0] == &b[0]) fmt.Println(a[0], &a[0]) 

It is output:

 &a == &b false &a[0] == &b[0] false {} &{} 

Or the same as your second case. This basically shows that the compiler decided to use different addresses. Given that "it can have the same address in memory," you should not rely on equality, since the exact behavior depends on the internal go elements and can change at any time.

For further reading, I would recommend this excellent article on empty structure .

+2
source

Source: https://habr.com/ru/post/1274430/


All Articles