Go語言之unsafe包介紹及使用

unsafe內容介紹

type ArbitraryType int
type Pointer *ArbitraryType
func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

unsafe包只有兩個類型,三個函數,可是功能很強大。html

unsafe 庫讓 golang 能夠像C語言同樣操做計算機內存,但這並非golang推薦使用的,能不用盡可能不用,就像它的名字所表達的同樣,它繞過了golang的內存安全原則,是不安全的,容易使你的程序出現莫名其妙的問題,不利於程序的擴展與維護。golang

先簡單介紹下Golang指針express

  1. *類型:普通指針,用於傳遞對象地址,不能進行指針運算。安全

  2. unsafe.Pointer:通用指針類型,用於轉換不一樣類型的指針,不能進行指針運算。函數

  3. uintptr:用於指針運算,GC 不把 uintptr 當指針,uintptr 沒法持有對象。uintptr 類型的目標會被回收ui

  unsafe.Pointer 能夠和 普通指針 進行相互轉換。this

  unsafe.Pointer 能夠和 uintptr 進行相互轉換。.net

也就是說 unsafe.Pointer 是橋樑,可讓任意類型的指針實現相互轉換,也能夠將任意類型的指針轉換爲 uintptr 進行指針運算。指針

uintptr這個類型,在golang中,字節長度也是與int一致。一般Pointer不能參與運算,好比你要在某個指針地址上加上一個偏移量,Pointer是不能作這個運算的,那麼誰能夠呢?就是uintptr類型了,只要將Pointer類型轉換成uintptr類型,作完加減法後,轉換成Pointer,經過*操做,取值,修改值,隨意。code

兩個類型簡介

// 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

ArbitraryType是int的一個別名,在Go中對ArbitraryType賦予特殊的意義。表明一個任意Go表達式類型。

Pointer 是int指針類型的一個別名,在Go中能夠把Pointer類型,理解成任何指針的父類型。

下面爲官方文檔中對Pointer的使用場景介紹

Pointer represents a pointer to an arbitrary type. There are four special operationsavailable for type Pointer that are not available for other types:
- A pointer value of any type can be converted to a Pointer.
- A Pointer can be converted to a pointer value of any type.
- A uintptr can be converted to a Pointer.
- A Pointer can be converted to a uintptr.

Pointer therefore allows a program to defeat the type system and read and write arbitrary memory. It should be used with extreme care.

golang的指針類型長度與int類型長度,在內存中佔用的字節數是同樣的.

ArbitraryType類型的變量也能夠是指針。因此,千萬不要死磕type後邊的那個int

三個函數簡介

// Sizeof takes an expression x of any type and returns the size in bytes
// of a hypothetical variable v as if v was declared via var v = x.
// The size does not include any memory possibly referenced by x.
// For instance, if x is a slice,  Sizeof returns the size of the slice
// descriptor, not the size of the memory referenced by the slice.
**func Sizeof(x ArbitraryType) uintptr**

// Offsetof returns the offset within the struct of the field represented by x,
// which must be of the form structValue.field. In other words, it returns the
// number of bytes between the start of the struct and the start of the field.
**func Offsetof(x ArbitraryType) uintptr**

// Alignof takes an expression x of any type and returns the required alignment
// of a hypothetical variable v as if v was declared via var v = x.
// It is the largest value m such that the address of v is always zero mod m.
// It is the same as the value returned by reflect.TypeOf(x).Align().
// As a special case, if a variable s is of struct type and f is a field
// within that struct, then Alignof(s.f) will return the required alignment
// of a field of that type within a struct. This case is the same as the
// value returned by reflect.TypeOf(s.f).FieldAlign().
**func Alignof(x ArbitraryType) uintptr**

經過分析發現,這三個函數的參數均是ArbitraryType類型,就是接受任何類型的變量。

  1. Alignof返回變量對齊字節數量
  2. Offsetof返回變量指定屬性的偏移量,這個函數雖然接收的是任何類型的變量,可是有一個前提,就是變量要是一個struct類型,且還不能直接將這個struct類型的變量看成參數,只能將這個struct類型變量的屬性看成參數。
  3. Sizeof 返回變量在內存中佔用的字節數,切記,若是是slice,則不會返回這個slice在內存中的實際佔用長度

示例

Sizeof

unsafe.Sizeof函數返回的就是uintptr類型的值(表達式,即值的大小):

var p float64 = 99
fmt.Println(reflect.TypeOf(unsafe.Sizeof(p)))
fmt.Println(unsafe.Sizeof(p))

results:

uintptr
8

unsafe.Sizeof接受任意類型的值(表達式),返回其佔用的字節數,在上面的例子中float64的大小是8bytes。

若是傳入一個指針類型的對象會返回多少呢?

type W struct {
    a byte
    b int32
    c int64
}

var w *W
fmt.Println(unsafe.Sizeof(w)) //4 or 8

通常狀況下,多是4或8,由於w是指針類型uintptr,而uintptr是平臺相關的,在32位系統下大小是4bytes,在64位系統下是8bytes。

要獲取值類型的大小,須要對指針變量進行取值:

fmt.Println(unsafe.Sizeof(*w)) //16

對齊

在上面的例子中,*w的大小爲16,按照常理來講,byte佔用1字節,int32佔用4字節,int64佔用8字節,大小應該是13纔對。這是由於發生了對齊,unsafe.Alignof能夠計算對齊值:

unsafe.Alignof(w.a)   // type byte
unsafe.Alignof(w.b)   // type int32 
unsafe.Alignof(w.c)   // type int64

分別是一、四、8,由於int32類型的對齊值是4bytes,必須是4的倍數,故byte類型要填充3個字節。而填充後,二者的大小和爲8bytes,int64對齊值是8bytes,不須要填充,因此用unsafe.Sizeof獲取到結構的大小爲4+4+8=16。

反射包的對齊方法

反射包也有某些方法可用於計算對齊值:

unsafe.Alignof(w)等價於reflect.TypeOf(w).Align。

unsafe.Alignof(w.i)等價於reflect.Typeof(w.i).FieldAlign()。

結構體的對齊值

若是咱們計算的是結構體的對齊值而不是某個字段或者基本類型,那麼值會是多少呢?

type W struct {
    a byte
    b int32
    c int64
}

var w *W
var w2 W
    
fmt.Println(unsafe.Alignof(w))
fmt.Println(unsafe.Alignof(w2))
fmt.Println(reflect.TypeOf(w).Elem().Align())

results:

8
8
8

64位機器下,指針對象的對齊值是8,由於指針類型是uintptr。而結構體的值類型倒是8bytes的對齊值,這是由於會先進行字段的對齊,字段最大的對齊值是8bytes,所以結構體值類型的對齊值也是8。

更改結構,驗證一下:

type W struct {
    a byte
    b int32
    c int32
}
var w W
fmt.Println(unsafe.Alignof(w)) //4

綜合示例

type T struct {
    t1 byte
    t2 int32
    t3 int64
    t4 string
    t5 bool
}

fmt.Println("----------unsafe.Pointer---------")
t := &T{1, 2, 3, "this is a example", true}
ptr := unsafe.Pointer(t)
t1 := (*byte)(ptr)
fmt.Println(*t1)
t2 := (*int32)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t2)))
*t2 = 99
fmt.Println(t)
t3 := (*int64)(unsafe.Pointer(uintptr(ptr) + unsafe.Offsetof(t.t3)))
fmt.Println(*t3)
*t3 = 123
fmt.Println(t)

results:

----------unsafe.Pointer---------
1
&{1 99 3 this is a example true}
3
&{1 99 123 this is a example true}

藉助於 unsafe.Pointer,咱們實現了像 C 語言中的指針偏移操做。能夠看出,這種不安全的操做使得咱們能夠在任何地方直接訪問結構體中未公開的成員,只要能獲得這個結構體變量的地址。

參考資料:

  1. https://blog.csdn.net/libing_thinking/article/details/49907815
  2. https://studygolang.com/articles/6903
  3. https://blog.csdn.net/hzwy23/article/details/60893765
  4. http://www.javashuo.com/article/p-whwxmzpq-ca.html
  5. https://studygolang.com/articles/1414
相關文章
相關標籤/搜索