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.
|
|
Output:
|
|
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.
|
|
Output:
|
|
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.