Decryption go:unsafe.Pointer

 

unsafe.Pointer

In the previous article, we compared the differences between three types of pointers in Go. You can check that out before diving into this one. Now, let’s summarize the distinctions between the three pointer types.

  • Ordinary pointers: They do not support pointer arithmetic, retain both address and type information, and the data they point to won’t be garbage collected.
  • unsafe.Pointer: They do not support pointer arithmetic, retain the address but not the type information, and the data they point to won’t be garbage collected.
  • uintptr: They support address arithmetic, retain the address but not the type information, and the data they point to will be garbage collected.

This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on Medium. Thank you very much.

Compared to C, Go is designed as a strongly-typed statically-typed language for ease of writing, high efficiency, and reduced complexity. Strongly-typed means once defined, a type cannot be changed, while statically-typed means type checking is done before runtime.

For safety reasons, Go doesn’t allow conversion between two pointer types. To address this, Go introduces unsafe.Pointer. unsafe.Pointer is a special kind of pointer that can hold addresses of any type, akin to the void* pointer in C, being versatile.

Example

In the official documentation, we find four rules regarding unsafe.Pointer:

  • Any pointer value of any type can be converted to a Pointer.
  • A Pointer can be converted to a pointer value of any type.
  • A uintptr can be converted to a Pointer.
  • A Pointer can be converted to a uintptr.

With unsafe.Pointer, we can achieve conversions between different types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

//go:nosplit
func convert(p *int) *int32 {
	return (*int32)(unsafe.Pointer(p))
}

func main() {
	var i int = 1
	q := convert(&i)
	fmt.Println(reflect.TypeOf(*q))
}

Output:

1
2
➜  unsafe git:(main) ✗ go run main.go
int32

We’ve successfully converted variables of different types using unsafe.Pointer.

For the third and fourth rules, let’s test them with a more complex example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
	"fmt"
	"unsafe"
)

type User struct {
	name string
	age  int32
}

func main() {
	user := &User{
		name: "Tom",
		age:  18,
	}
	fmt.Println(*user)
	pName := (*string)(unsafe.Pointer(user))
	*pName = "Bob"
	age := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(user)) + unsafe.Offsetof(user.age)))
	*age = 20
	fmt.Println(*user)
}

Output:

1
2
3
➜  unsafe git:(main) ✗ go run main.go
{Tom 18}
{Bob 20}

We’ve manipulated the values of different fields by offsetting in memory. Firstly, when modifying the name field of user, since name is the first field, no offset is required. We obtain the pointer to user, then use unsafe.Pointer to convert it to *string for the assignment. Secondly, when modifying the age field of user, we need an offset because age isn’t the first field. We convert the pointer address of user to uintptr, then use unsafe.Offsetof(u.age) to get the offset needed, perform address arithmetic (+) to offset. Now, the address points to the age field of user, and to assign it, we need to convert uintptr to *int32. Thus, by converting uintptr to unsafe.Pointer and then to *int32, we can proceed with the operation. Ultimately, we’ve achieved pointer operations akin to C in Go.

Conclusion

unsafe is indeed unsafe, hence it should be used sparingly, especially in memory manipulation. This circumvents Go’s built-in safety mechanisms and improper operations could lead to memory corruption, which can be very challenging to debug.

unsafe.Pointer is mainly used during Go’s compilation phase. Because it allows bypassing the type system to directly access memory, it offers higher efficiency. If we decompile the convert function, we’d find that it merely changes the MOVQ instruction to MOVL, without introducing any additional instructions.

Licensed under CC BY-NC-SA 4.0
Last updated on Jun 27, 2024 16:41 CST
Built with Hugo
Theme Stack designed by Jimmy