Go study notes (74) — unsafe of the Go standard library

GoLanguage comes with unsafeadvanced usage of the package, as the name suggests, unsafeis unsafe. GoDefining it as the package name is also to let us not use it as much as possible. But while insecurity, it also has the advantage that can bypass Gothe security mechanisms of memory, direct memory read and write. So sometimes out of performance needs, it is still risky to use it to operate on memory.

1. Pointer type conversion

GoIt is a strongly typed static language. Strong typing means that once defined, the type cannot be changed; static means that type checking is done before execution. At the same time, for security reasons, the Golanguage does not allow conversion of two pointer types.

We generally use *Tas a pointer type represents a type pointing Tpointer variable. For safety reasons, two different pointer types can not conversion, such as *intnot turn *float64.

Let's look at the following code:

func main() {
    
    
   i:= 10
   ip:=&i
   var fp *float64 = (*float64)(ip)
   fmt.Println(fp)
}

When this code is compiled, it will prompt

cannot convert ip (type * int) to type * float64

That is, forced transformation cannot be carried out. What if it still needs to be converted? This requires the use of unsafethe bag Pointera.

unsafe.PointerIs a special significance pointer, can represent any type of address, similar to the Clanguage of the void*pointer, it is versatile.

Under normal circumstances, *intit can not be converted *float64, but by unsafe.Pointerdoing transit on it. In the following example, by unsafe.Pointerthe *intconversion to *float64, and new *float64for 3 times multiplication operation, you will find that the original value of the variable i is also changed, becomes 30.

func main() {
    
    
	i := 10
	ip := &i
	var fp *float64 = (*float64)(unsafe.Pointer(ip))
	*fp = *fp * 3
	fmt.Println(*ip) // 30
}

Illustrated by unsafe.Pointerthis universal pointer, we can in *Tbetween doing any conversion. So unsafe.Pointerwhat in the end is? Why other types of indicators can be converted to unsafe.Pointerit? That depends on unsafe.Pointerthe source code is defined as follows:

// ArbitraryType is here for the purposes of documentation
// only and is not actually part of the unsafe package. 
// It represents the type of an arbitrary Go expression.
type ArbitraryType int
type Pointer *ArbitraryType

By Golanguage official notes, ArbitraryTypeyou can represent any type (where ArbitraryTypeonly the documents required, should not be too concerned about it themselves, as long as you can remember that you can represent any type). And unsafe.Pointeragain *ArbitraryType, that unsafe.Pointerany type of pointer, which is a common type of pointer, sufficient to represent any memory address.

2. uintptr pointer type

uintptrIt is also a pointer type, which is large enough to represent any pointer. Its type definition is as follows:

// uintptr is an integer type that is large enough 
// to hold the bit pattern of any pointer.
type uintptr uintptr

Now that you have unsafe.Pointer, why design uintptrtype it? This is because unsafe.Pointernot for operation, does not support such as + (plus) operator operation, but uintptrcan be. Through it, the pointer offset can be calculated, so that specific memory can be accessed, and the purpose of reading and writing to specific memory can be achieved. This is a real memory-level operation.

In the following code, the pointer offset by modifying structan example of field structures in the body, the demo uintptrusage.

func main() {
    
    
	p := new(person)
	//Name是person的第一个字段不用偏移,即可通过指针修改
	pName := (*string)(unsafe.Pointer(p))
	*pName = "wohu"
	//Age并不是person的第一个字段,所以需要进行偏移,这样才能正确定位到Age字段这块内存,才可以正确的修改
	pAge := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + unsafe.Offsetof(p.Age)))
	*pAge = 20
	fmt.Printf("p is %#v", *p) // p is main.person{Name:"wohu", Age:20}
}

type person struct {
    
    
	Name string
	Age  int
}

This example is not to directly access the corresponding field personstructure field assignment, but offset by a pointer to find the corresponding memory, and the memory operation of assignment.

The operation steps are described in detail below.

  1. First use newthe function to declare a *persontype of pointer variable p.

  2. Then *personthe type of pointer variables pby unsafe.Pointerconversion to *stringthe type of pointer variables pName.

  3. Because personthe first field of the structure is the stringtype Name, so pNamethat the pointer points to Namea field (offset 0), to pNamemodify actually modify the field Namevalue.

  4. Because the Agefield is not personthe first field, it must be carried out to modify the pointer offset calculation. It is necessary to put a pointer variable pby unsafe.Pointer convert uintptr, in order to address arithmetic.

Since the pointer offset is to be performed, how much does it need to offset? This offset can function unsafe.Offsetofcalculated, the function returns a uintptrtype of offset, this offset would have the + operator can obtain the correct Agememory address field, that is, by unsafe.Pointerthe converted *intType of pointer variable pAge.

Note that then, if the pointer arithmetic to be performed, first by unsafe.Pointerconversion to uintptrthe type of pointer. After completion pointer arithmetic, but also by unsafe.Pointerthe conversion to a real pointer type (such as in the example *int, this value can be assigned or operation on this memory type).

  1. With point field Agepointer variable pAge, assignment may be, modify the field Agevalue of.

This example is to explain the main uintptrpointer arithmetic, so the assignment is written in a field structure is so complex, according to the normal coding, the above example code and the following code as a result.

func main() {
    
    
   p :=new(person)
   p.Name = "wohu"
   p.Age = 20
   fmt.Println(*p)
}

The core of pointer arithmetic is that it operates on each memory address. Through the increase or decrease of the memory address, you can point to different pieces of memory and operate on them, and you don’t need to know what name (variable name) this piece of memory is given. .

3. Pointer conversion rules

You already know Gothree types of pointers exist in the language, they are: common *T, unsafe.Pointerand uintptr. Through the above examples, the conversion rules of these three can be summarized:

  • Any type *Tcan be converted to unsafe.Pointer;
  • unsafe.PointerCan also be converted to any type *T;
  • unsafe.PointerIt can be converted uintptr;
  • uintptrCan also be converted to unsafe.Pointer;

It can be found that it is unsafe.Pointermainly used for the conversion of pointer types, and it is a bridge for the conversion of various pointer types. uintptrMainly used for pointer arithmetic, especially to locate different memory by offset.

4. unsafe.Sizeof

SizeofFunction returns the size of memory occupied by a type, and only the type of this size, regardless of type and content corresponding to the size of the storage variable, such as booltype of one byte, int8is also one byte.

By SizeofYou can view the function of any type (such as strings, slice, integer) memory size, the following sample code:

func main() {
    
    
	fmt.Println(unsafe.Sizeof(true))                 // 1
	fmt.Println(unsafe.Sizeof(int8(0)))              // 1
	fmt.Println(unsafe.Sizeof(int16(0)))             // 2
	fmt.Println(unsafe.Sizeof(int32(0)))             // 4
	fmt.Println(unsafe.Sizeof(int64(0)))             // 8
	fmt.Println(unsafe.Sizeof(int(0)))               // 8
	fmt.Println(unsafe.Sizeof(string("张三")))         // 16
	fmt.Println(unsafe.Sizeof([]string{
    
    "李四", "张三"})) // 24
}

For integer, the number of bytes occupied means that the size of this type of digital storage scope, such as int8one byte, that is 8bit, so it can store the size range is -128~~127, that is −2^(n-1)to 2^(n-1)−1. Which nrepresent bit, int8represent 8bit, int16represent 16bit, and so on.

For platform and related inttypes, depending on platform 32-bit or 64-bit, it will take the biggest. For example, I own tests above output, you will find intand int64the size is the same, because I use the 64-bit computer platforms.

Tip: a structfootprint the size of the structure, equal to that contained in the field type and the size of the memory footprint.

Summary: The
unsafebag is the most commonly used Pointerindicator by which you can make *T, uintptrand Pointeramong the three converters in order to achieve their needs, such as zero memory copy or by uintptrconduct pointer arithmetic, which can improve program efficiency.

unsafeAlthough the functions in the package are not safe, they are really fragrant, such as pointer arithmetic, type conversion, etc., which can help us improve performance. However, I recommend not to use as much as possible, because it can bypass the Gochecks language compiler, you may encounter problems because of operational errors occur. Of course, if necessary to improve the operating performance is required, or may be used, such as []byteturn string, can pass unsafe.Pointerzero memory copy.

5. The difference between uintptr and unsafe.Pointer

  • unsafe.Pointer It is just a simple general pointer type, used to convert different types of pointers, and it cannot participate in pointer operations;
  • And uintptrfor pointer arithmetic, GCnot to uintptrwhen the pointer, that uintptrcan not hold the object, uintptrthe type of target will be recycled;
  • unsafe.Pointer Can be converted with ordinary pointers;
  • unsafe.PointerIt can uintptrbe converted to each other;
package main

import (
	"fmt"
	"unsafe"
)

type W struct {
    
    
	b int32
	c int64
}

func main() {
    
    
	var w *W = new(W)
	//这时w的变量打印出来都是默认值0,0
	fmt.Println(w.b, w.c)

	//现在我们通过指针运算给b变量赋值为10
	b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w.b))
	*((*int)(b)) = 10
	//此时结果就变成了10,0
	fmt.Println(w.b, w.c)
}
  • uintptr(unsafe.Pointer(w))Get a wpointer to the start value;
  • unsafe.Offsetof(w.b)Obtaining bthe offset variable;
  • The two added to the obtained baddress value, the general purpose pointer Pointeris converted into a pointer particular ((*int)(b)), by *the symbol value, then the assignment. *((*int)(b))Corresponding to the (*int)(b)converted int, and finally re-assignment to a variable 10, so that the completed pointer arithmetic.

Guess you like

Origin blog.csdn.net/wohu1104/article/details/113795624