博主信息
虎小弟
博文
1
粉絲
0
評論
0
訪問量
242
積分:0
P豆:2

Go的內存對齊和指針運算詳解和實踐

2020年01月13日 20:02:02閱讀數:242博客 / 虎小弟 / 技術人技術事

uintptr 和 unsafe普及

uintptr

在Go的源碼中uintptr的定義如下:

  1. /* uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
  2. 從英文注釋可以看出 uintptr是一個整形,它的大小能夠容納任何指針的位模式,它是無符號的,最大值為:18446744073709551615,怎么來的,int64最大值 * 2 +1
  3. */
  4. type uintptr uintptr

位模式:內存由字節組成.每個字節由8位bit組成,每個bit狀態只能是0或1.所謂位模式,就是變量所占用內存的所有bit的狀態的序列
指針大小:一個指針的大小是多少呢?在32位操作系統上,指針大小是4個字節,在64位操作系統上,指針的大小是8字節,
所以uintptr能夠容納任何指針的位模式,總的說uintptr表示的指針地址的值,可以用來進行數值計算
GC不會把uintptr當作指針,uintptr不會持有一個對象,uintptr類型的目標會被GC回收

unasfe

在Go中,unsafe是一個包,內容也比較簡短,但注釋非常多,這個包主要是用來在一些底層編程中,讓你能夠操作內存地址計算,也就是說Go本身是不支持指針運算,但還是留了一個后門,而且Go也不建議研發人員直接使用unsafe包的方法,因為它繞過了Go的內存安全原則,是不安全的,容易使你的程序出現莫名其妙的問題,不利于程序的擴展與維護但為什么說它呢,因為很多框架包括SDK中的源代碼都用到了這個包的知識,在看源代碼時這塊不懂,容易懵。下面看看這個包定義了什么?

  1. //ArbitraryType的類型也是int,但它被賦予特殊的含義,代表一個Go的任意表達式類型
  2. type ArbitraryType int
  3. //Pointer是一個int指針類型,在Go種,它是所有指針類型的父類型,也就是說所有的指針類型都可以轉化為Pointer, uintptr和Pointer可以相互轉化
  4. type Pointer *ArbitraryType
  5. //返回指針變量在內存中占用的字節數(記住,不是變量對應的值占用的字節數)
  6. func Sizeof(x ArbitraryType) uintptr
  7. /*Offsetof返回變量指定屬性的偏移量,這個函數雖然接收的是任何類型的變量,但是有一個前提,就是變量要是一個struct類型,且還不能直接將這個struct類型的變量當作參數,只能將這個struct類型變量的屬性當作參數*/
  8. func Offsetof(x ArbitraryType) uintptr
  9. //返回變量對齊字節數量
  10. func Alignof(x ArbitraryType) uintptr

什么是內存對齊?為什么要內存對齊?

在我了解比較深入的語言中(Java Go)都有內存對齊的概念,百度百科對內存對齊的概念是這樣定義的:“內存對齊”應該是編譯器的“管轄范圍”。編譯器為程序中的每個“數據單元”安排在適當的位置上,所謂的數據單元其實就是變量的值。

為什么要內存對齊呢?

  1. 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常(32位平臺上運行64位平臺上編譯的程序要求必須8字節對齊,否則發生panic)
  2. 性能原因:數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問

對齊規則:也就是對齊的邊界,多少個字節內存對齊,在32位操作系統上,是4個自己,在64位操作系統上是8個字節

通過一幅圖來理解上面的內容,下圖只是舉個例子,位數并沒有畫全

指針運算和內存對齊實踐

內存對齊實踐

理論總是枯燥的,但必須了解,也許看了理論還是不懂,接下來通過實踐讓你明白

  1. //創建一個變量
  2. var i int8 = 10
  3. //建一個變量轉化成Pointer 和 uintptr
  4. p := unsafe.Pointer(&i) //入參必須是指針類型的
  5. fmt.Println(p) //是內存地址0xc0000182da
  6. u := uintptr(i)
  7. fmt.Println(u) //結果就是10
  8. //Pointer轉換成uintptr
  9. temp := uintptr(p)
  10. //uintptr轉Pointer
  11. p= unsafe.Pointer(u)
  12. //獲取指針大小
  13. u = unsafe.Sizeof(p) //傳入指針,獲取的是指針的大小
  14. fmt.Println(u) // 打印u是:8
  15. //獲取的是變量的大小
  16. u = unsafe.Sizeof(i)
  17. fmt.Println(u) //打印u是:1
  18. //創建兩個個結構體
  19. type Person1 struct{
  20. a bool
  21. b int64
  22. c int8
  23. d string
  24. }
  25. type Person2 struct{
  26. b int64
  27. c int8
  28. a bool
  29. d string
  30. }
  31. //接下來演示一下內存對齊,猜一猜下面l兩個打印值是多少呢?
  32. person1 := Person1{a:true,b:1,c:1,d:"spw"}
  33. fmt.Println(unsafe.Sizeof(person1))
  34. person2 := Person2{b:1,c:1,a:true,d:"spw"}
  35. fmt.Println(unsafe.Sizeof(person2))
  36. //第一個結果是40,第二個結果是32,為什么會有這些差距呢?其實就是內存對齊做的鬼,我來詳細解釋一下

我們知道在Person1和Person2種變量類型都一樣,只是順序不太一樣,
bool占1個字節,
int64占8個字節,
int8占一個字節,
string占用16個字節,
總的結果應該是 1+8+1+16= 26,為啥Person1是40呢,Person2是32,看下圖

根據上圖,我們就明白了,在結構體編寫中存在內存對齊的概念,而且我們應該小心,盡可能的避免因內存對齊導致結構體大小增大,在書寫過程中應該讓小字節的變量挨著。我們可以工具進行檢測(golangci-lint)。

我們可以通過func Alignof(x ArbitraryType) uintptr這個方法返回內存對齊的字節數量,如下代碼

  1. type Person1 struct{
  2. a bool
  3. b int64
  4. c int8
  5. d string
  6. }
  7. p := Person{a:true,b:1,c:1,d:"spw"}
  8. fmt.Println(unsafe.Alignof(person))
  9. type Person2 struct{
  10. a bool
  11. c int8
  12. }
  13. p1 := Person1{a:true,b:1,c:1,d:"spw"}
  14. fmt.Println(unsafe.Alignof(p1))
  15. p2 := Person2{a:true,c:1}
  16. fmt.Println(unsafe.Alignof(p2))
  17. //你任務上面兩個println打印多少呢?結果是8,1,在結構體中,內存對齊是按照結構體中最大字節數對齊的(但不會超過8)
指針運算實踐

我們還是用代碼來舉例說明

  1. type W struct {
  2. b int32
  3. c int64
  4. }
  5. var w *W = new(W)
  6. //這時w的變量打印出來都是默認值0,0
  7. fmt.Println(w.b,w.c)
  8. //現在我們通過指針運算給b變量賦值為10
  9. b := unsafe.Pointer(uintptr(unsafe.Pointer(w)) + unsafe.Offsetof(w.b))
  10. *((*int)(b)) = 10
  11. //此時結果就變成了10,0
  12. fmt.Println(w.b,w.c)

解釋一下上面的代碼
uintptr(unsafe.Pointer(w))獲取了w的指針起始值,
unsafe.Offsetof(w.b) 獲取b變量的偏移量
兩個相加就得到了b的地址值,將通用指針Pointer轉換成具體指針((*int)(b)),通過 符號取值,然后賦值,((int)(b)) 相當于把(int) 轉換成 int了,最后對變量重新賦值成10,這樣指針運算就完成了。

全部評論

文明上網理性發言,請遵守新聞評論服務協議

條評論
暫無評論暫無評論!
  • 3d试机号绕胆图