Hello everyone, I am fried fish.
When you are learning Go, you must have learned the knowledge point "Go pointers do not support pointer arithmetic and conversion". why?
First of all, Go is a static language, and all variables must be of scalar type. Different types cannot perform cross-type operations such as assignment and calculation.
Then the pointer also corresponds to the relative type, which is also within the scope of Compile's static type checking. At the same time, static languages are also called strong typing. That is, once it is defined, it cannot be changed.
Wrong example
func main(){
num := 5
numPointer := &num
flnum := (*float32)(numPointer)
fmt.Println(flnum)
}
Output result:
# command-line-arguments
...: cannot convert numPointer (type *int) to type *float32
In the example, we created a num
variable with a value of 5 and a type of int
, ready to do something big.
Next, after taking the pointer address for it, we tried to force the conversion to *float32
, but the result failed...
Universal breaking unsafe
For the "wrong example" just now, we can use today's actor unsafe
standard library to solve it. It is a magical package. In the official interpretation, it is summarized as follows:
- Revolve around Go program memory safety and type operations.
- It is likely to be non-portable.
- Not protected by the Go 1 compatibility guide.
Simply put, it is not recommended for you to use it because it is unsafe (unsafe).
But in special scenes, using it can break Go's type and memory safety mechanism, allowing you to get a surprise effect that shines.
unsafe.Pointer
In order to solve this problem, unsafe.Pointer
. It represents an addressable pointer value of any type and can be converted between different pointer types (similar to the use of void * in the C language).
It contains four core operations:
- Any type of pointer value can be converted to Pointer.
- Pointer can be converted to any type of pointer value.
- uintptr can be converted to Pointer.
- Pointer can be converted to uintptr.
In this part, focus on the first and second points. Do you think about how to modify the "wrong example" to make it run?
Modify as follows:
func main(){
num := 5
numPointer := &num
flnum := (*float32)(unsafe.Pointer(numPointer))
fmt.Println(flnum)
}
Output result:
0xc4200140b0
In the above code, we make minor changes. The pointer variable is modified through unsafe.Pointer
, and the pointer conversion of any type (*T) can be completed.
It should be noted that at this time, the variable cannot be manipulated or accessed, because it is not known what type of thing the pointer address points to. I don’t know what type it is, but how can it be parsed?
If it cannot be resolved, it will naturally not be able to be changed.
unsafe.Offsetof
In the previous section, we modified the ordinary pointer variables. So can it do more complicated things?
type Num struct{
i string
j int64
}
func main(){
n := Num{i: "EDDYCJY", j: 1}
nPointer := unsafe.Pointer(&n)
niPointer := (*string)(unsafe.Pointer(nPointer))
*niPointer = "煎鱼"
njPointer := (*int64)(unsafe.Pointer(uintptr(nPointer) + unsafe.Offsetof(n.j)))
*njPointer = 2
fmt.Printf("n.i: %s, n.j: %d", n.i, n.j)
}
Output result:
n.i: 煎鱼, n.j: 2
Before analyzing what this code does, we need to understand some basic concepts of structure:
- The member variable of the structure is a continuous memory in the memory storage.
- The initial address of the structure is the memory address of the first member variable.
- Calculate the offset based on the member address of the structure. You can get the memory addresses of other member variables.
Come back and look at the above code again to get the execution flow:
- Modify the value of
n.i
i
is the first member variable. Therefore, there is no need to calculate the offset, just take out the pointer and convert it toPointer
, and then force the conversion to a pointer value of string type. - Modify the value of
n.j
j
is the second member variable. The offset calculation is required before the memory address can be modified. After performing the offset operation, the current address has pointed to the second member variable. Then repeat the conversion assignment.
Detailed analysis
It should be noted that the following methods are used here (to complete the target of the offset calculation):
1. uintptr: uintptr
is a built-in type of Go. Returns an unsigned integer, which can store a complete address. Subsequent often used for pointer arithmetic
type uintptr uintptr
2. unsafe.Offsetof: Returns the offset of the member variable x in the structure. More specifically, it returns the number of bytes from the initial position of the structure to x. It should be noted that the reference ArbitraryType
represents any type, not the defined int
. It actually serves as a placeholder
func Offsetof(x ArbitraryType) uintptr
In this part, the third and fourth characteristics Pointer
At this time, the variables can be manipulated.
Bad example
func main(){
n := Num{i: "EDDYCJY", j: 1}
nPointer := unsafe.Pointer(&n)
...
ptr := uintptr(nPointer)
njPointer := (*int64)(unsafe.Pointer(ptr + unsafe.Offsetof(n.j)))
...
}
There is a problem here. The uintptr
type cannot be stored in a temporary variable. Because from the point of view of GC, uintptr
is just an unsigned integer, and it is not known that it is a pointer address.
Therefore, when certain conditions are met, ptr
may be garbage collected. Then, isn't the next memory operation a mystery?
If you have any questions, welcome feedback and exchanges in the comment area. The best relationship between . Your is fried fish . Thank you for your support.
The article is continuously updated, and you can read it on search [The brain is fried fish]. This article 1617a35681435f GitHub github.com/eddycjy/blog has been included. To learn the Go language, you can see Go learning map and route . Welcome to Star to remind you.
Summarize
Briefly review two knowledge points, as follows:
- The first is that
unsafe.Pointer
can make your variables go around in different pointer types, that is, expressed as any addressable pointer type. - The second is that
uintptr
often used in conjunction withunsafe.Pointer
for pointer calculations, very cleverly.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。