Photo from @unsplash

The basic meaning of a pointer is the memory address where some value is stored.

In Golang, not all values are addressable (although they are also stored in memory, such as const), but all variables are necessarily addressable.

Variables are values stored in an area of memory [1]. In addition to the familiar var x int where x is a variable, a more complex expression can also represent a variable, such as sliceA[0], mapB[“key”], and structc.fieldd. That is, they can all have their own Pointers.

But here’s the question: If the value of a variable changes, does its pointer change?

If the address of the variable does not change, then the pointer will not change. So, the question becomes, does changing the value of a variable change the memory location of a variable? The answer is no change [2].

We know that if a variable is of pointer type, it can store a value of pointer type. For example, the PTR in var PTR *int can store a value of pointer type. The value of this variable can change so that only different memory Spaces are available, but only the value of this variable can change. The memory space of the PTR itself is unchanged, that is, & PTR is always the same value (unless moving GC occurs). Similarly, the question we mentioned above is similar to asking var a int, if I change the value of a, does ampersand a change? The answer is no.

Code examples are as follows:

b := 1
fmt.Printf("%p\n", &b) // 0x416028
b = 2
fmt.Printf("%p\n", &b) // 0x416028
c := &b
fmt.Printf("%p\n", c) // 0x416028
Copy the code

As you can see, b’s memory address is always the same.

But there is a problem here. If the value of a variable changes, does the value that its pointer points to (or draws from) change?

The answer is obviously going to change. Because the pointer to the variable still points to the same memory address, but the value at that address has changed. Here’s an example:

type A struct {
    Value int
}
a := A{Value: 1}
fmt.Printf("a-ptr: %p, value-ptr: %p, value: %d\n", &a, &a.Value, (&a).Value)
// a-ptr: 0x41602c, value-ptr: 0x41602c, value: 1
a = A{Value: 2}
fmt.Printf("a-ptr: %p, value-ptr: %p, value: %d\n", &a, &a.Value, (&a).Value)
// a-ptr: 0x41602c, value-ptr: 0x41602c, value: 2
Copy the code

As you can see, the Pointers remain the same (since the Value field is the first field of struct A, so the memory address is the same), even though we reassign the Value to variable A.

Golang is different from C

Pointers in Golang differ (or are optimized) in two ways compared to C:

1. Go creates a pointer to a struct directly

Golang := &a {Value: 1} PTR := &a {Value: 1} In C, however, it cannot be obtained by a separate assignment statement:

typedef struct { int value; } A; A *ptr1; A *ptr2 = &a {1}; // no such syntax A A = {1}; // We can get a pointer by &aCopy the code

If this difference is just a grammatical one, another may be a bug one.

2. Go can safely return a pointer to a local variable

In the C code example above, we can indeed declare some variables, but if the declarations are done within a method, for example:

A *init()
{
    A *ptr;
    return ptr;
}
Copy the code

or

A *init()
{
    A a;
    return &a;
}
Copy the code

The declared local variable is an automatic variable[3] that “disappears” after the init() method is completed [4].

For versions that declare Pointers directly, we do the following:

A *init(int value)
{
    A *ptr;
    printf("1. inside - ptr: %x, value: %d\n", ptr, ptr->value);
    return ptr;
}
int main()
{
    A *ptr = init(1);
    printf("2. after return: ptr: %x, value: %d\n", ptr, ptr->value);
}
Copy the code

The result might look something like this:

1. inside - ptr: 1ad2f248, value: 25
2. after return - ptr: 1ad2f248, value: 25
Copy the code

Are the results unexpected (slightly different on different machines)? We do declare A variable of type pointer, but the value of the variable, the actual stored memory address, does not necessarily point to A structure A, and may well be an entirely unrelated address. This leaves the program vulnerable, especially if the address accidentally accessed contains some important data.

Of course, the address may also be invalid if you want to change the value in the address, such as:

 ptr->value = 2;
Copy the code

You are likely to get some errors, such as a bus error on macOS. That is, the program has a problem trying to manipulate the value on this memory address.

Similarly, a method that declares the value of a structure and then returns a pointer has unintended consequences. We did the following experiment;

A *init(int value)
{
    A a = {value};
    printf("1. inside - ptr: %x, value; %d\n", &a, (&a)->value);
    return &a;
}

int main() {
    A *ptr = init(1);
    printf("2. after return - ptr: %x, value: %d\n", ptr, ptr->value);
    printf("3. after return - ptr: %x, value: %d\n", ptr, ptr->value);
    A *ptr2 = init(2)
    printf("4. after return - ptr: %x, value: %d\n", ptr, ptr->value); // Watch here!!!
}
Copy the code

You’ll find something like this (even more similar if it’s macOS) :

1. inside - ptr: e43de2d8, value: 1
2. after return - ptr: e43de2d8, value: 1
3. after return - ptr: e43de2d8, value: 0
1. inside - ptr: e43de2d8, value: 2
4. after return - ptr: e43de2d8, value: 2
Copy the code

The printed Pointers all have the same value (that is, the address), but the structure members have strange values. Specifically, the value of the same address is repeatedly accessed, and the result is unexpectedly different. The specific reason for this is related to the call stack structure of the program, but we want to clarify here:

After a method returns, its local variable has disappeared, although the memory address is still there, but it is best not to use the memory address again! Accessing the address of an automatic variable that has disappeared can be very buggy, because the value of the address may have been changed by other code! — This kind of problem is often called use after free.

To generate a pointer to a structure inside a C method, use malloc:

A *ptr = (A *)malloc(sizeof(A));
Copy the code

It is then safe to return the pointer.

In Golang, by contrast, the processing is much simpler and that portion of memory is not reclaimed:

func init(value int) *A {
    return &A{Value: 1}
}
Copy the code

So, this go code is safe.

Pointer arithmetic

Many Golang programs use Pointers but do not add or subtract them, unlike C programs. Golang’s official introductory learning tool (Go Tour) even states that GO does not support pointer arithmetic. This is not the case, but I don’t think I’ve ever seen a pointer operation in a normal GO program (well, I know you want to write something unusual).

In fact, go does Pointer arithmetic by converting Pointers to uintptr numbers via unsafe.Pointer. Note here that uintptr is an integer type, not a pointer type.

Such as:

uintptr(unsafe.Pointer(&p)) + 1
Copy the code

I get the position of the next byte of ampersand p. However, as suggested by Go Programming Language, it is best to convert this computed memory address directly to a pointer type:

unsafe.Pointer(uintptr(unsafe.Pointer(&p) + 1))
Copy the code

Because of the garbage collection mechanism in GO, if a GC moves the memory address of the target value, the pointer value stored as an integer becomes invalid.

Also note that a + 1 on a pointer in Go really only points to the next byte, whereas a + 1 or ++ in C, taking into account the length of the data type, automatically points to the next byte after the end of the current value (or, possibly, the beginning of the next value). To achieve the same effect in GO, use the safe.sizeof method:

unsafe.Pointer(uintptr(unsafe.Pointer(&p) + unsafe.Sizeof(p)))
Copy the code

Finally, another common pointer operation is to convert pointer types. This can also be done with the broadening package:

var a int64 = 1
(*int8)(unsafe.Pointer(&a))
Copy the code

If you haven’t encountered the need to convert pointer types, take a look at this project (Port Scanning Tool), which builds the IP protocol header code using pointer type conversions.


  1. A variable is a piece of storage containing a value. — Donovan, Alan A. A.. The Go Programming Language (Addison-Wesley Professional Computing Series) (p.32). Pearson Education.Kindle Edition. ↩

  2. The memory address of the variable remains the same ↩ unless an underlying program such as the Moving GC (such as a copy algorithm for memory collection) occurs

  3. Each local variable in a function comes into existence only when the function is called, and disappears when the function is exited. This is why such variables are usually known as automatic variables, following terminology in other languages. — Kernighan, Brian W.. C Programming Language (P.31).Pearson Education.Kindle Edition. ↩

  4. The specific process and principle of the disappearance are still to be checked. It may be recycled by the garbage ↩