为什么所有嵌套的struct对象在打印出时都具有相同的地址?

I am trying to find out in a := b if a will be a different copy of struct like func pass_by_value(a some_struct)

But I find that I don't understand the print statement.

Consider this go playground

    nested_level2 := test_assign_nested_level2{}
    nested_level1 := test_assign_nested{nested_level2}
    top_level := test_assign{nested_level1}

    assign := top_level
    fmt.Println("top_level address")
    fmt.Printf("%p", &top_level)
    fmt.Println(" ")
    fmt.Println("1 level address")
    fmt.Printf("%p", &top_level.Level1)
    fmt.Println(" ")
    fmt.Println("2 level address")
    fmt.Printf("%p", &top_level.Level1.Level_2)
    fmt.Println("------------------------")

    fmt.Println("assign top_level address")
    fmt.Printf("%p", &assign)
    fmt.Println(" ")
    fmt.Println("1 level address")
    fmt.Printf("%p", &assign.Level1)
    fmt.Println(" ")
    fmt.Println("2 level address")
    fmt.Printf("%p", &assign.Level1.Level_2)

The output of the above is

top_level address
0x10410020 
1 level address
0x10410020 
2 level address
0x10410020 
assign top_level address
0x10410024 
1 level address
0x10410024 
2 level address
0x10410024

I expect the output to be similar to

    fmt.Println("top_level address")
    fmt.Printf("%p", &top_level)
    fmt.Println(" ")
    fmt.Println("1 level address")
    fmt.Printf("%p", &nested_level1)
    fmt.Println(" ")
    fmt.Println("2 level address")
    fmt.Printf("%p", &nested_level2)
    fmt.Println(" ")
    fmt.Println(" ------------------------------- ")

where

top_level address
0x421152280 
1 level address
0x421152270 
2 level address
0x421152260 

each struct have a different address. but it seems like the child struct have the same address as the parent struct.

Why does all the nested element in the struct have the same address?

And does := actually copy a new struct recursively? Like the print statement indicated? (ie. := will return a brand new copy of struct with each of its field content also a brand new copy recursively)

My guess is that you are looking for something like this:

package main

import "fmt"

// Nesting: type T2 struct{ F3 int }
type T2 struct{ F3 int }

// Nesting: type T1 struct{ { F2 struct{ F3 int } }
type T1 struct{ F2 T2 }

// Nesting: type T0 struct{ F1 struct{ F2 struct{ F3 int } } }
type T0 struct{ F1 T1 }

func main() {
    t2 := T2{F3: 42}
    fmt.Printf(
        "%p %p %d
",
        // 0xc4200120d0 0xc4200120d0 42
        &t2, &t2.F3, t2.F3,
    )

    t1 := T1{F2: t2}
    fmt.Printf(
        "%p %p %p %d
",
        // 0xc4200120f0 0xc4200120f0 0xc4200120f0 42
        &t1, &t1.F2, &t1.F2.F3, t1.F2.F3,
    )

    t0 := T0{F1: t1}
    fmt.Printf(
        "%p %p %p %p %d
",
        // 0xc420012100 0xc420012100 0xc420012100 0xc420012100 42
        &t0, &t0.F1, &t0.F1.F2, &t0.F1.F2.F3, t0.F1.F2.F3,
    )
}

Output:

0xc4200120d0 0xc4200120d0 42
0xc4200120f0 0xc4200120f0 0xc4200120f0 42
0xc420012100 0xc420012100 0xc420012100 0xc420012100 42

For T0

type T0 struct{ F1 T1 }

type T1 struct{ F2 T2 }

type T2 struct{ F3 int }

is equivalent to

type T0 struct {
    F1 struct {
        F2 struct {
            F3 int
        }
    }
}

and T0, F1, F2, and F3 have the same address.


In your new example, T0

type T2 struct{ F3A, F3B int }

type T1 struct{ F2 T2 }

type T0 struct{ F1 T1 }

is equivalent to

type T0 struct {
    F1 struct {
        F2 struct {
            F3A int
            F3B int
        }
    }
}

and T0, F1, F2, and F3A have the same address. F3A and F3B have different addresses.

Types in Go are not autonomes wrappers around other things but just two things: Types allow to attach methods and types provide a layout in memory. For the question here the ability to attach methods to types is irrelevant. Let's take a look a neutral formulation of the question:

type A int64
type B { a A }
type C { b B }

This declares three types with the following memory layout:

  • Type A has the memory layout of a int64 (that is 8 bytes).
  • Type B has the memory layout of a single A, that is a int64, so 8 bytes.
  • Type C has the memory layout of a single B, that is a single A, that is a int64, so 8 bytes.

Type B in regard to memory layout is not a "wrapper" around A, at least the wrapper adds absolutely nothing. From a purely memory-layout perspective defining type B would be useless (but it allows to attach different methods to B than to A).

Now it should be clear that the address of a c C is that of the first of its 8 bytes and this is the same as c.b's address which is the same than c.b.a. And any assignment to a C is just a copy of one machine word (on 64bit architectures).

If you define type D { a A; b B } it gets more interesting as a D is now 16 bytes long. (Adding more to a D might even leave holes due to padding.) But still does D not provide anything (expect a new method set) to an A and a B adjacent to each other in memory.