Go language has three types of pointers. In the normal development process, we only encounter the ordinary pointer. However, in the low-level source code of the Go language, there are a lot of operations involving three types of pointer conversion and manipulation. Let’s clarify these points first.
In the C language, pointer are crucial. Although pointers make operations highly flexible and efficient, there are many security risks associated with accessing memory through pointer operations, such as accessing memory out of bounds and compromising the atomicity of types in the type system. Here are some examples of incorrect usage:
|
|
The reason for these security risks in the C language is that it supports pointer operations and pointer type conversions. Therefore, in Go language, the most commonly used ordinary pointers, which have types, have eliminated pointer arithmetic and type conversion operations to ensure type safety. Here’s an example:
|
|
This ensures that pointers always point to valid addresses with allocated memory and preserves type independence and atomicity.
In addition to ordinary pointers, Go language also retains two other types of pointers that allow bypassing the type system and achieving the same level of memory manipulation as in C language. The other two types of pointers are:
unsafe.Pointer
uintptr
To understand these two, we need to establish a concept: a pointer is essentially a number that stores a memory address. The addressing space is 32 bits for a 32-bit machine and 64 bits for a 64-bit machine, so the size of a pointer is equal to the number of bits in the machine.
uintptr
is straightforward; it is simply a number that stores a memory address. It is equivalent to uint32
a 32-bit machine and uint64
on a 64-bit machine. Since it is a number, it naturally supports arithmetic operations, which allows it to represent any memory location. However, the problem is that data cannot be operated solely based on its memory address; you also need to know its size. In other words, we cannot manipulate data solely based on a uintptr
pointer. On the other hand, an ordinary typed pointer not only provides the address but also informs the compiler about the size of the data pointed to. For example, *int32
and *int64
pointers tell the compiler that they operate on 4-byte and 8-byte data, respectively.
Now that we have explained ordinary pointers and uintptr
pointers in Go language, what is this additional unsafe.Pointer
compared to C language?
unsafe.Pointer
is a generic pointer that, like uintptr
, only keeps the memory address without concerning itself with the type. However, the difference between unsafe.Pointer
and uintptr
is that the former refers to an object that will be referenced by the garbage collector (GC), so it will not be collected as garbage by the GC. In contrast, the latter only represents the memory address as a number, which means that if a data address is saved by uintptr
, it will be mercilessly collected by the garbage collector.
Summary of the three types of pointers in Go language:
- Ordinary pointer: This does not support pointer arithmetic, saves the address and type information, and the data it points to will not be garbage collected by the GC.
unsafe.Pointer
: Does not support pointer arithmetic, saves the address but not the type information, and the data it points to will not be garbage collected by the GC.uintptr
: Supports address arithmetic, saves the address but not the type information, and the data it points to will be garbage collected by the GC.
In practical usage, uintptr
cannot be directly converted to an ordinary pointer, and both must be first converted to unsafe.Pointer
as an intermediate step before further conversion.
Here’s a simple example:
|
|