简体   繁体   English

空结构片的地址

[英]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: 我有一个关于empty struct的基本问题,试图在尝试获取两个切片的后备数组元素的地址时得到以下不同的输出:

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: 大小为0:

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

Returns 0 . 返回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. 给定“可能在内存中具有相同的地址”,您不应依赖于相等性,因为确切的行为取决于go内部结构,并且随时可能更改。

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. 在您的第一个示例中,切片的后备数组的地址仅在main()函数内使用,它们不会逸出到堆中。 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. 这些只是bool值,不包括地址值。 So the compiler chooses to use the same address for the backing array of a and b . 因此,编译器选择对ab的后备数组使用相同的地址。

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. 在您的第二个示例中,后备数组的地址(更具体地说是后备数组的某些元素的地址)在main()函数外部使用,它们被传递到fmt.Println()函数中并在内部使用,因为您也打印这些地址。

We can "prove" this by passing the -gcflags '-m' params to the Go tool, asking it to print the result of the escape analysis. 我们可以通过将-gcflags '-m'参数传递给Go工具来“证明”这一点,要求它打印转义分析的结果。

In your first example, saving your code in play.go , running the go run -gcflags '-m' play.go command, the output is: 在第一个示例中,将代码保存在play.go ,运行go run -gcflags '-m' play.go命令,输出为:

./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: 使用第二个示例运行go run -gcflags '-m' play.go ,输出为:

./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. 正如可以看到, a[0] &a[0]逃逸到堆,所以背衬阵列a动态分配,并且因此将具有比的不同的地址b的。

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 : 让我们修改第二个示例,以使第三个变量c的地址也不会被打印出来,然后将bc进行比较:

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: 在此运行go run -gcflags '-m' play.go ,输出为:

./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 . 由于只有&a[0]被印刷,但不&b[0]也不&c[0]从而&a[0] == &b[0]将是false ,但&b[0] == &c[0]将是true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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