Pointers in Go: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 114: Line 114:
The '''referencing operator''', also known as the ampersand operator, returns an address, also known as a "reference", from a variable. <code>&</code> should be read as "address of ...". The address is represented internally as an instance of type <code>pointer</code>. The address points to the location in memory where the instance associated with the "referenced" variable is stored.
The '''referencing operator''', also known as the ampersand operator, returns an address, also known as a "reference", from a variable. <code>&</code> should be read as "address of ...". The address is represented internally as an instance of type <code>pointer</code>. The address points to the location in memory where the instance associated with the "referenced" variable is stored.
<syntaxhighlight lang='go'>
<syntaxhighlight lang='go'>
&<variable-name>
&<variable_name>
</syntaxhighlight>
</syntaxhighlight>



Revision as of 20:36, 8 May 2024

External

Internal

TODO

Further reading:

Overview

A pointer is a data type that represents a virtual address in memory, usually the address of a location in memory that is referred by a variable.

A pointer can be declared as such:

var aPtr *int // a pointer to an int

A pointer can also be implicitly declared using the short variable declaration and the the referencing operator inside functions:

a := 10
aPtr := &a

aPtr is a variable that contains the memory address of the memory location associated with the variable a. Changing the memory value using a syntax that involves the pointer will surface in the value of the variable:

*aPtr = 20
println(a) // will display 20

Escape Analysis

Once a non-nil value is assigned to a pointer, the Go runtime guarantees that the thing being pointed to will continue to be valid for the life time of the pointer. This allows for a pattern when what looks like a stack variable can be allocated inside a function, and a pointer to it returned outside the function. The pointer will remain valid even if the stack is unwound, the compiler will arrange for the memory location holding the value of i to be valid after the function return. This is done with escape analysis, which is the process of determining whether a variable should be stored on stack or on the heap:

func makeInt() *int {
  i := 10
  return &i
}

go build -gcflags="-m" cmd/acmd.go
[...]
cmd/acmd.go:4:2: moved to heap: i 

How to Tell if a Variable is a Pointer

Use reflect.TypeOf() on the variable. If the variable is a pointer, displaying the result of reflect.TypeOf() will start with "*":

var b *int
fmt.Println(reflect.TypeOf(b)) // will print "*int"

Alternatively, use:

fmt.Printf("%#v\n", b) // will print "(*int)(nil)"

Displaying Pointers

To display the value at memory address stored in the pointer, must dereference:

fmt.Printf("%d\n", *aPtr)

To display the memory address stored in the pointer in a hexadecimal notation, with the "0x" prefix, use %p or %v, they are equivalent for pointers:

fmt.Printf("%p\n", aPtr)
fmt.Printf("%v\n", aPtr) // same thing

This will print:

0xc000012080

For more details on the pointer, including the type of the data it points to, use:

fmt.Printf("%#v\n", aPtr)

This will print:

(*int)(0xc000012080)

Pointers can be also represented using the "%X" format specifier, which displays the pointer in base 16, upper case characters, without the "0x" prefix:

fmt.Printf("%X\n", aPtr)

This will print:

C000094018

Pointer Variable Name

Review of existing code has shown that people do not use special variable names to indicate that the variable contains a pointer. someName seems to be perfectly fine, and someNamePtr does not seem to be required. This is in part because the compiler knows how to handle transparently the difference between the values and pointers in some common cases. For example, a struct field is referred with .<field_name> regardless of whether the variable is a pointer to the structure or contains the struct value:

type SomeStruct struct {
  i int
}

s := SomeStruct{10}
s2 := &s

fmt.Printf("%d\n", s.i)
fmt.Printf("%d\n", s2.i)

Also see:

Go Language | Variable Names

When to Use Values and When to Use Pointers

Pointer Operators

The pointer data type comes with two operators: & (the referencing operator), and * (the dereferencing operator).

The Referencing Operator &

The referencing operator, also known as the ampersand operator, returns an address, also known as a "reference", from a variable. & should be read as "address of ...". The address is represented internally as an instance of type pointer. The address points to the location in memory where the instance associated with the "referenced" variable is stored.

&<variable_name>
color := "blue"
pointerToColor := &color
println(pointerToColor) // prints "0xc000058720"


It works with variables and also with literals. The syntax &user{name:"Bill"} where user is a struct is legal.

The Dereferencing Operator *

The dereferencing operator (star operator) takes a pointer and returns the value in memory the pointer's address points toward. The variable must contain a pointer type instance, otherwise the code will not compile. The value thus exposed can be read or written.

*<pointer-name>
color := "blue"
pointerToColor := &color
println(*pointerToColor) // prints "blue"
*pointerToColor = "red"
println(color) // prints "red"