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]
Best solution is to pass an empty structure because it will only increment a counter in the channel but not assign memory, copy elements and so on.
1) To check if the structure is empty:fmt. Println( "It is an empty structure." ) fmt. Println( "It is not an empty structure." )
An empty structIt occupies zero bytes of storage. var s struct{} fmt.Println(unsafe.Sizeof(s)) // prints 0. As the empty struct consumes zero bytes, it follows that it needs no padding. Thus a struct comprised of empty structs also consumes no storage.
To declare the type for a variable that holds a slice, use an empty pair of square brackets, followed by the type of elements the slice will hold.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With