Tsuji Daishiro
Tsuji Daishiro

Reputation: 425

In Go, is a structure pointer not a reference type?

I don't understand the behavior of Go variables. I want you to tell me. See the example implementation below.

package main

import (
    "fmt"
)

func pointer(ip *Num) {
    fmt.Printf("pointer type [%T] : %p\n", &ip, &ip)
}

func pointerpointer(ip **Num) {
    fmt.Printf("pointerpointer type [%T] : %p\n", ip, ip)
}

func main() {
    pnum := &Num{i: 3}
    fmt.Printf("main type [%T] : %p\n", &pnum, &pnum)
    pointer(pnum)
    pointerpointer(&pnum)
}

type Num struct {
    i int
}

https://play.golang.org/p/LxDAgopxeh0

main type [**main.Num] : 0x40c138
pointer type [**main.Num] : 0x40c148
pointerpointer type [**main.Num] : 0x40c138

I store the struct Num pointer as a variable [pnum]. The address that can be acquired when this is passed to the pointer function is different from the address that can be acquired in the main function. Why??

It has been confirmed that the same address as the main function can be obtained by referring to the pointer pointer as the pointerpointer function.

Upvotes: 2

Views: 2432

Answers (3)

Volker
Volker

Reputation: 42431

In Go, is a structure pointer not a reference type?

No, simply because there are no reference types in Go.

There are a few types with reference semantics, slices being the most prominent example, but even a slice is a value type and not a reference type.

Pointers in Go a plain values and really machine level addresses. When you pass around a memory address there is no "reference stuff" going on. And if you store the memory address in a variable you can take the address of that variable. Again nothing referency-like. It's values all the way down in Go.

It is basically like in C.

Upvotes: 5

user10753492
user10753492

Reputation: 778

The short answer is:

Pointers are passed by Value.

The function pointer receives a copy of the memory address. It stores that copy somewhere (possibly its own stack, or something).

The memory address is just a number.

If you define a number in main and take its address, and then you pass that number to another function and take its address, should you expect both addresses to be the same?

Upvotes: 4

torek
torek

Reputation: 489113

pnum, in main, is an actual variable: a box, floating in memory, containing a pointer of type *Num.

 0x40c138
+--------+
|    *---|--->
+--------+

To what does pnum point? Well, Num{i: 3} created an unnamed variable, floating in memory somewhere. We haven't printed it. I modified your Playground example to add one more fmt.Printf call to find out:

main type [**main.Num] : 0x40c138
pnum points to [*main.Num] : 0x40e020

or:

 0x40c138       0x40e020
+--------+     +--------+
|    *---|---> |  i: 3  |
+--------+     +--------+

Now let's move on: main calls pointer, passing to it the value stored in pnum—the 0x40c138 value which lets the computer find the box holding the unnamed instance holding i=3. This value is stored in memory somewhere. Where is that somewhere? Why, it's the number you printed, because you print &ip. Fortunately the Playground is very deterministic, so it's also the one I printed:

pointer type [**main.Num] : 0x40c148

So we can add another box to our drawing:

 0x40c138       0x40e020
+--------+     +--------+
|0x40e020|---> |  i: 3  |
+--------+     +--------+
                   ^
 0x40c148          |
+--------+         |
|0x40e020|---------+
+--------+

Now pointer returns, discarding the box at 0x40c148 that holds 0x40e020, and you call pointerpointer, passing &pnum as a value. Function pointerpointer now runs with a box at some location. Where is that location? We don't know; we never print it. We do print what's in the box at that location, and what's in the box is 0x40c138:

 0x40c138       0x40e020
+--------+     +--------+
|0x40e020|---> |  i: 3  |
+--------+     +--------+
    ^
    |
 ????????
+--------+
|0x40c138|
+--------+

We can add one more fmt.Printf to the program to find where this last box actually is in memory and run again:

pointerpointer's &ip is [***main.Num] : 0x40c150

so replace the eight question marks with 0x40c150.

The overall rule here is simple enough: Every variable has some existence somewhere, unless the compiler can optimize it away. Taking the address of the variable, with &x, tends to prevent the compiler from optimizing the variable itself away. The type of &x is pointer to ___ where the blank is the type of x. The value of &x is the address of the variable.

In this case, you

  • made an anonymous (unnamed) Num instance,
  • took its address (forcing the compiler to put it into memory somewhere), and
  • set a variable pnum to that value

(all in the first line of main). Then you took the address of pnum itself, which means pnum also has to live somewhere in memory. Those are the two boxes we drew initially. You printed that address's type and value on your second line.

You then passed the value stored in pnum to func pointer, which stored the value in a variable. So that created another, separate variable, which has its own address.

Without worrying about what function pointer did, you then passed the address of pnum to func pointerpointer. This address is a value, and pointerpointer stored that value in a variable. This, too, created another separate variable.

Throughout all of this, the address of the anonymous Num with i=3 never moved. Neither did the address of pnum itself.

Upvotes: 9

Related Questions