goland basic package - use of unsafe

Keywords: Go Back-end goland

Warm tips: suitable for students with memory operation experience

1, The role of unsafe

According to the definition of golang, unsafe is a type safe operation. As the name suggests, it should be used very carefully; Unsafe can be dangerous, but it can also be very useful. For example, when using system calls and Go structures that must have the same memory layout as C structures, you may have no choice but to use unsafe. For pointer operation, there are four descriptions in the official definition of unsafe package:

  1. Any type of Pointer can be converted to Pointer

  2. Pointer can be converted to any type of pointer

  3. uintptr can be converted to Pointer

  4. Pointer can be converted to uintptr

An additional rule is added: pointers to different types of data cannot be directly converted to each other. They must be converted with the help of unsafe. Pointer (void pointer similar to C), that is, the above 1 and 2 rules. give an example:

func Float64bits(f float64) uint64 {
  // Unable to convert directly, error: convert expression of type * float64 to type * Uint64
  // return *(*uint64)(&f)   
​
 // First convert * float64 to Pointer (description 1), and then convert Pointer to * Uint64 (Description 2)
  return *(*uint64)(unsafe.Pointer(&f)) 
}

Key words: null pointer type void of C language*

2, Definition of unsafe:

The overall code is relatively simple, with 2 type definitions and 3 uintptr return functions

package unsafe
//ArbitraryType is only used for document purposes and is not actually part of the unsafe package. It represents the type of any Go expression.
type ArbitraryType int
​
//Any type of pointer, similar to * void of C
type Pointer *ArbitraryType
​
//Determines the exact size of the structure in memory
func Sizeof(x ArbitraryType) uintptr
​
//Returns the offset of a field in a structure
func Offsetof(x ArbitraryType) uintptr
​
//Return the value of a field in the structure (the reason for byte alignment)
func Alignof(x ArbitraryType) uintptr
 Here is an example to help understand:

package main
​
import (
    "fmt"
    "unsafe"
)
​
type Human struct {
    sex  bool   // one byte
    age  uint8  // one byte
    uAge uint8  // four byte
    min  int    // eight byte
    name string // sixteen byte
}// thirty two byte
​
func main() {
    h := Human{
        true,
        30,
        31,
        2,
        "hello",
    }
    i := unsafe.Sizeof(h)
    j := unsafe.Alignof(h.sex)
    k := unsafe.Offsetof(h.sex)
    fmt.Println(i, j, k)
    fmt.Println("size:",j,"pos:", k)
    j = unsafe.Alignof(h.age)
    k = unsafe.Offsetof(h.age)
    fmt.Println("size:",j,"pos:", k)
    j = unsafe.Alignof(h.uAge)
    k = unsafe.Offsetof(h.uAge)
    fmt.Println("size:",j,"pos:", k)
    j = unsafe.Alignof(h.min)
    k = unsafe.Offsetof(h.min)
    fmt.Println("size:",j,"pos:", k)
    j = unsafe.Alignof(h.name)
    k = unsafe.Offsetof(h.name)
    fmt.Println("size:",j,"pos:", k)
    fmt.Printf("%p\n", &h)
    var p unsafe.Pointer
    p = unsafe.Pointer(&h)
    fmt.Println(p)
}
​
// output :
//32 1 0
//size: 1 pos: 0
//size: 1 pos: 1
//size: 1 pos: 2
//size: 8 pos: 8
//size: 8 pos: 16
//0xc0000be000
//0xc0000be000
​

According to the byte pair, we can easily conclude that the size of Human is 32 bytes

3, Pointer usage

As mentioned earlier, a pointer is a pointer of any type and can point to any type of data. Refer to the conversion of Float64bits and the unsafe. Pointer (& H) of the above chestnuts, so it is mainly used to convert various types

4, uintptr

In golang, uintptr is defined as type uintptr uintptr. Uintptr is a built-in type of golang and an integer that can store pointers

  1. According to description 3, an unsafe.Pointer can also be converted to uintptr type, and then saved to a pointer type numeric variable (Note: This is only the same numeric value as the current pointer, not a pointer), and then used for necessary pointer value operations. (uintptr is an unsigned integer number, enough to hold an address)

  2. Although this conversion is also reversible, turning uintptr to an unsafe.Pointer pointer may break the type system, because not all numbers are valid memory addresses.

  3. Many operations that turn an unsafe.Pointer pointer to uintptr and then back to an unsafe.Pointer type pointer are also unsafe. For example, the following example needs to convert the address of variable x plus the address offset of field B into a pointer of type * int16, and then update x.b through this pointer:

package main
​
import (
    "fmt"
    "unsafe"
)
​
func main() {
​
    var x struct {
        a bool
        b int16
        c []int
    }
​
    /**
    unsafe.Offsetof The argument to the function must be a field x.f, 
Then return the offset of the f field relative to the starting address of x, including possible holes
    */
​
    /**
    uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
    Pointer operation
    */
    // And Pb: = & x.b equivalent
    pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
    *pb = 42
    fmt.Println(x.b) // "42"
}

Although the above writing is cumbersome, it is not a bad thing here, because these functions should be used very carefully. Don't try to introduce a temporary variable of uintptr type, because it may destroy the security of the code (Note: This is a real example of why the unsafe package is not safe).

The following code is wrong:

// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42

The cause of the error is subtle.

Sometimes the garbage collector will move some variables to reduce problems such as memory fragmentation. This type of garbage collector is called mobile GC. When a variable is moved, all pointers holding the old address of the change amount must be updated to the new address after the variable is moved at the same time. From the perspective of garbage collector, an unsafe.Pointer is a pointer to a variable. Therefore, when the variable is moved, the corresponding pointer must also be updated; However, the temporary variable of uintptr type is just an ordinary number, so its value should not be changed. The above error code introduces a non pointer temporary variable tmp, which makes the garbage collector unable to correctly recognize that it is a pointer to variable x. When the second statement is executed, the variable x may have been transferred, and the temporary variable tmp is no longer the current & x.b address. The third assignment statement to the previously invalid address space will completely destroy the whole program!

An inappropriate analogy:

God let A know S, A told C the name of B, and C also knew that there was B.

Then S is renamed B, not S, and God will tell A about it. But God won't tell C about it.

So C couldn't find S again, and then C blew up

 

Friendship guest: God = GC, A = = pointer (* int16), C= unsafe.Pointer, S(B) = address space

Posted by rob323 on Fri, 03 Dec 2021 00:32:08 -0800