Go
Language comes with unsafe
advanced usage of the package, as the name suggests, unsafe
is unsafe. Go
Defining 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 Go
the 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
Go
It 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 Go
language does not allow conversion of two pointer types.
We generally use *T
as a pointer type represents a type pointing T
pointer variable. For safety reasons, two different pointer types can not conversion, such as *int
not 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 unsafe
the bag Pointer
a.
unsafe.Pointer
Is a special significance pointer, can represent any type of address, similar to the C
language of the void*
pointer, it is versatile.
Under normal circumstances, *int
it can not be converted *float64
, but by unsafe.Pointer
doing transit on it. In the following example, by unsafe.Pointer
the *int
conversion to *float64
, and new *float64
for 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.Pointer
this universal pointer, we can in *T
between doing any conversion. So unsafe.Pointer
what in the end is? Why other types of indicators can be converted to unsafe.Pointer
it? That depends on unsafe.Pointer
the 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 Go
language official notes, ArbitraryType
you can represent any type (where ArbitraryType
only 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.Pointer
again *ArbitraryType
, that unsafe.Pointer
any type of pointer, which is a common type of pointer, sufficient to represent any memory address.
2. uintptr pointer type
uintptr
It 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 uintptr
type it? This is because unsafe.Pointer
not for operation, does not support such as + (plus) operator operation, but uintptr
can 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 struct
an example of field structures in the body, the demo uintptr
usage.
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 person
structure 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.
-
First use
new
the function to declare a*person
type of pointer variablep
. -
Then
*person
the type of pointer variablesp
byunsafe.Pointer
conversion to*string
the type of pointer variablespName
. -
Because
person
the first field of the structure is thestring
typeName
, sopName
that the pointer points toName
a field (offset 0), topName
modify actually modify the fieldName
value. -
Because the
Age
field is notperson
the first field, it must be carried out to modify the pointer offset calculation. It is necessary to put a pointer variablep
byunsafe.Pointe
r convertuintptr
, 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.Offsetof
calculated, the function returns a uintptr
type of offset, this offset would have the + operator can obtain the correct Age
memory address field, that is, by unsafe.Pointer
the converted *int
Type of pointer variable pAge
.
Note that then, if the pointer arithmetic to be performed, first by unsafe.Pointer
conversion to uintptr
the type of pointer. After completion pointer arithmetic, but also by unsafe.Pointer
the conversion to a real pointer type (such as in the example *int
, this value can be assigned or operation on this memory type).
- With point field
Age
pointer variablepAge
, assignment may be, modify the fieldAge
value of.
This example is to explain the main uintptr
pointer 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 Go
three types of pointers exist in the language, they are: common *T
, unsafe.Pointer
and uintptr
. Through the above examples, the conversion rules of these three can be summarized:
- Any type
*T
can be converted tounsafe.Pointer
; unsafe.Pointer
Can also be converted to any type*T
;unsafe.Pointer
It can be converteduintptr
;uintptr
Can also be converted tounsafe.Pointer
;
It can be found that it is unsafe.Pointer
mainly used for the conversion of pointer types, and it is a bridge for the conversion of various pointer types. uintptr
Mainly used for pointer arithmetic, especially to locate different memory by offset.
4. unsafe.Sizeof
Sizeof
Function 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 bool
type of one byte, int8
is also one byte.
By Sizeof
You 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 int8
one 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 n
represent bit
, int8
represent 8bit
, int16
represent 16bit
, and so on.
For platform and related int
types, depending on platform 32-bit or 64-bit, it will take the biggest. For example, I own tests above output, you will find int
and int64
the size is the same, because I use the 64-bit computer platforms.
Tip: a struct
footprint the size of the structure, equal to that contained in the field type and the size of the memory footprint.
Summary: The
unsafe
bag is the most commonly used Pointer
indicator by which you can make *T
, uintptr
and Pointer
among the three converters in order to achieve their needs, such as zero memory copy or by uintptr
conduct pointer arithmetic, which can improve program efficiency.
unsafe
Although 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 Go
checks 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 []byte
turn string
, can pass unsafe.Pointer
zero 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
uintptr
for pointer arithmetic,GC
not touintptr
when the pointer, thatuintptr
can not hold the object,uintptr
the type of target will be recycled; unsafe.Pointer
Can be converted with ordinary pointers;unsafe.Pointer
It canuintptr
be 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 aw
pointer to the start value;unsafe.Offsetof(w.b)
Obtainingb
the offset variable;- The two added to the obtained
b
address value, the general purpose pointerPointer
is converted into a pointer particular((*int)(b))
, by*
the symbol value, then the assignment.*((*int)(b))
Corresponding to the(*int)(b)
convertedint
, and finally re-assignment to a variable 10, so that the completed pointer arithmetic.