go如何進行指針運算

package main

import (
	"fmt"
	"unsafe"
)

type S1 struct {
	A int32
	B int64
}

func main() {
	s := S1{}
	fmt.Println(s)
	b := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.B)))
	*b = 1
	fmt.Println(s)
}

輸出:

{0 0}
{0 1}
 

像c裏面直接操作指針是不可行的,但是go裏面提供了unsafe包,可以達到這個目的,慎用,只做瞭解就可以了。

Pointer

type ArbitraryType int
type Pointer *ArbitraryType

這裏特別要注意ArbitraryType在這裏只起到文檔作用,不是代表是真正的int

  1. ArbitraryType is here for the purposes of documentation only and is not actually part of the unsafe package.

  2. It represents the type of an arbitrary Go expression.

比如:sizeof等相關的函數可以接受任意類型的變量,假如按照int類型來解釋,基本就解釋不通了

官方對該類型有四個重要的描述

1.任何類型的指針值都可以轉換爲Pointer // unsafe.Pointer(&a1)

2.Pointer可以轉換爲任何類型的指針值 // (*float)(unsafe.Poniter(&a1))

3.uintptr可以轉換爲Pointer // unsafe.Poniter(uintptr(nPointer) + unsafe.Sizeof(&a)*3)

4.Pointer可以轉換爲uintptr // uintptr(unsafe.Pointer(&a1))

總上很像c語言裏面的void*

uintptr

type uintptr uintptr

uintptr 是一個整數類型,它足夠大,可以存儲。只有將Pointer轉換成uintptr才能進行指針的相關操作

但下面的用法是錯誤的

tmp := uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.B)
pb := (*int64)(unsafe.Pointer(tmp))
*pb = 1

uintptr是可以用於指針運算的,但是GC並不把uintptr當做指針,所以uintptr不能持有對象,可能會把GC回收,導致出現無法預知的錯誤,Pointer指向一個對象時,GC是不會回收這個內存的。

上面錯誤的代碼因爲引入一個非指針的臨時變量tmp,導致垃圾收集器無法正確識別這個是一個指向變量s的指針。當第二個語句執行時,變量s可能已經被轉移,這時候臨時變量tmp也就不再是現在的&s.B地址。第三個向之前無效地址空間的賦值語句將徹底摧毀整個程序!

相關函數

sizeof

返回類型x所佔據的字節數,但是卻不包括x所引用的內存,例如,對於一個指針,函數返回的大小爲8字節(64位),一個slice的大小則爲slice header的大小

	var a int32 = 10
	t := unsafe.Sizeof(a)
	fmt.Println(t)

	var b string = "test"
	fmt.Println(unsafe.Sizeof(b))

	type aa struct {
		B bool
		C uint64
	}

	c := aa{}
	fmt.Println(unsafe.Sizeof(c))

	type bb struct {
		B *bool
	}
	c1 := bb{}
	fmt.Println(unsafe.Sizeof(c1))
4// 這個沒啥說的, int32在64位機器上佔4個字節
16// Sizeof(string)佔計算string header的大小, struct string { uint8 *str; int len;}
16// 這個涉及到內存對齊的問題
8// 指針佔8個字節(64位機器)

Offsetof

返回結構體中某個字段的偏移量,這個字段必須是structValue.field形式,也就是返回結構體變量的開始位置到那個字段的開始位置之間字節數(包括內存對齊)

	type aa struct {
		B bool
		C uint64
	}
	c := aa{}
	fmt.Println(unsafe.Offsetof(c.C))

輸出是8, aa裏面8字節對齊, 大小16,每個成員變量8個字節大小

Alignof

這個函數返回內存對齊時的對齊值,跟內存對齊有關,正好可以驗證上面的是不是8字節對齊

	type aa struct {
		B bool
		C uint64
	}
	c := aa{}
	fmt.Println(unsafe.Alignof(c))


8

確實是8字節對齊

內存對齊

網上說的很多,在這提一下比較容易忘記的點:有的時候我們調整結構體字段的順序是可以節省內存的

type Part1 struct {
	a bool
	b int32
	c int8
	d int64
	e byte
}
type Part2 struct {
	e byte
	c int8
	a bool
	b int32
	d int64
}

func main() {
	part1 := Part1{}
	part2 := Part2{}
	fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
	fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))
}

$ go run main.go
part1 size: 32, align: 8
part2 size: 16, align: 8

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章