简体   繁体   中英

Addresses of slices of empty structs

I have a basic question on empty struct s and am trying to get my head around the following different outputs I am getting when attempting to get the address of backing arrays' elements 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, considering the following slightly changed 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 please explain the reason for the above difference? Thank you!

[Follow Up] Making the following modifications:

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()
}

Still returns the same output:

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

Though, uncommenting the method calls, returns :

{} &{}
&a == &b false
&a[0] == &b[0] true
0x19583c // ==> [depends upon env]
0x19583c // ==> [depends upon env]

An empty struct is basically: pretty much nothing. It's of size 0:

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

Returns 0 .

Now, as to why the addresses of two empty structs are sometimes the same and sometimes not, this is down to the go internals. The only clue we can get is from the spec :

A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.

Note also the following code, with the first print moved to last:

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

This outputs:

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

Or the same as your second case. Basically, this shows that the compiler chose to use different addresses. Given the "may have the same address in memory", you should not rely on equality as the exact behavior depends on go internals and may change at any time.

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

Before going any deeper, know that according to the spec, a program is correct regardless of whether it yields equal or different addresses for values having zero size, as the spec only states that they may be the same, but does not require them to be the same.

Spec: Size and alignment guarantees:

A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.

So what you experience is an implementation detail. There are more details and factors to the decisions made, the following explanation is valid and sufficient only to your concrete examples:

In your first example addresses of the backing array(s) for your slices are only used inside the main() function, they don't escape to the heap. What you print is only the result of the comparison of the addresses. These are just bool values, they don't include the address values. So the compiler chooses to use the same address for the backing array of a and b .

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

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

In your first example, saving your code in play.go , running the go run -gcflags '-m' play.go command, 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 can see, addresses don't escape.

Running go run -gcflags '-m' play.go with your second example, the output is:

./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] escape to heap, so the backing array of a is dynamically allocated, and thus it will have a different address than that of b 's.

Let's "prove" this further. Let's modify your second example, to have a third variable c whose address will also not be printed, and let's compare b to 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, the output is:

./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] is printed but not &b[0] nor &c[0] , thus &a[0] == &b[0] will be false but &b[0] == &c[0] will be true .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM