golang中的unsafe.Pointer,指针,引用

在golang的源码中到处都能看到unsafe.Pointer的使用,它允许程序绕过类型系统读写任意内存,使用它时必须谨慎。

golang中基本的地址操作
var a int
b := &a // 取得a的地址 0xc00000c0a8
c := *b // 读取地址指向的内存空间

fmt.Printf("%#v\n", b) // (*int)(0xc00000a0b8)
func mm5() {
	b := Conf{}

	mm6(b)
    fmt.Println(b) // {}

	mm7(&b)
    fmt.Println(b) // {x}

	mm8(&b)
    fmt.Println(b) // {y}
}
func mm6(y Conf) {
	y.Name = "r"
}
func mm7(y *Conf) {
	y.Name = "x"
}
func mm8(y *Conf) {
	*y = Conf{"y"} // 向 y 指向的内存空间赋值
	y = &Conf{"z"} // 向 y 赋值,指向了另外一个内存地址
}

指针不仅仅是一个地址,它还带有原变量的类型

var a int
var b *float64
b = &a // error
unsafe包

unsafe 包用于编译阶段可以绕过 Go 语言的类型系统,直接操作内存。例如,利用 unsafe 包操作一个结构体的未导出成员。unsafe 包让我可以直接读写内存的能力。

// 实际上是整型,用来存储任意类型的指针的值
// 可以进行整型相关的运算,加减乘除取模比较等等
// Pointer 和 uintprt 可以相互转换 uintptr(Pointer)
type uintptr uintptr

type IntegerType int

// ArbitraryType是int的一个别名,在 Go 中ArbitraryType有特殊的意义,意为任意类型
type ArbitraryType int

// 可以把任意指针类型转换成unsafe.Pointer类型,unsafe.Pointer(&a)
// 虽然表现是也是整型数值的,但是不能进行整型的运算,需要先转换成 uintptr 再去运算
type Pointer *ArbitraryType

// Sizeof接受任意类型的值(表达式),返回其占用的字节数
// 任意变量
func Sizeof(x ArbitraryType) uintptr

// 返回结构体成员在内存中的位置距离结构体起始处的字节数,所传参数必须是结构体的
// 成员(结构体指针指向的地址就是结构体起始处的地址,即第一个成员的内存地址)
// 任意变量
// unsafe.Sizeof(b.Name)
func Offsetof(x ArbitraryType) uintptr

// 返回变量是按多少字节对齐的
// 任意变量
// unsafe.Alignof(wg.state1)
func Alignof(x ArbitraryType) uintptr

// 向 ptr 追加一定的长度
// 等价于 Pointer(uintptr(ptr) + uintptr(len))
func Add(ptr Pointer, len IntegerType) Pointer

// 返回一个切片,切片的起始位置为 ptr,切片的长度为 len
// 等价于 (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

下面重点看看Pointer
// Pointer represents a pointer to an arbitrary type. There are four special operations
// available 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.
//
// The following patterns involving Pointer are valid.
// Code not using these patterns is likely to be invalid today
// or to become invalid in the future.
// Even the valid patterns below come with important caveats.
//
// Running "go vet" can help find uses of Pointer that do not conform to these patterns,
// but silence from "go vet" is not a guarantee that the code is valid.
//
// (1) Conversion of a *T1 to Pointer to *T2.
//
// Provided that T2 is no larger than T1 and that the two share an equivalent
// memory layout, this conversion allows reinterpreting data of one type as
// data of another type. An example is the implementation of
// math.Float64bits:
//
//	func Float64bits(f float64) uint64 {
//		return *(*uint64)(unsafe.Pointer(&f))
//	}
//
// (2) Conversion of a Pointer to a uintptr (but not back to Pointer).
//
// Converting a Pointer to a uintptr produces the memory address of the value
// pointed at, as an integer. The usual use for such a uintptr is to print it.
//
// Conversion of a uintptr back to Pointer is not valid in general.
//
// A uintptr is an integer, not a reference.
// Converting a Pointer to a uintptr creates an integer value
// with no pointer semantics.
// Even if a uintptr holds the address of some object,
// the garbage collector will not update that uintptr's value
// if the object moves, nor will that uintptr keep the object
// from being reclaimed.
//
// The remaining patterns enumerate the only valid conversions
// from uintptr to Pointer.
//
// (3) Conversion of a Pointer to a uintptr and back, with arithmetic.
//
// If p points into an allocated object, it can be advanced through the object
// by conversion to uintptr, addition of an offset, and conversion back to Pointer.
//
//	p = unsafe.Pointer(uintptr(p) + offset)
//
// The most common use of this pattern is to access fields in a struct
// or elements of an array:
//
//	// equivalent to f := unsafe.Pointer(&s.f)
//	f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
//
//	// equivalent to e := unsafe.Pointer(&x[i])
//	e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
//
// It is valid both to add and to subtract offsets from a pointer in this way.
// It is also valid to use &^ to round pointers, usually for alignment.
// In all cases, the result must continue to point into the original allocated object.
//
// Unlike in C, it is not valid to advance a pointer just beyond the end of
// its original allocation:
//
//	// INVALID: end points outside allocated space.
//	var s thing
//	end = unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(s))
//
//	// INVALID: end points outside allocated space.
//	b := make([]byte, n)
//	end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))
//
// Note that both conversions must appear in the same expression, with only
// the intervening arithmetic between them:
//
//	// INVALID: uintptr cannot be stored in variable
//	// before conversion back to Pointer.
//	u := uintptr(p)
//	p = unsafe.Pointer(u + offset)
//
// Note that the pointer must point into an allocated object, so it may not be nil.
//
//	// INVALID: conversion of nil pointer
//	u := unsafe.Pointer(nil)
//	p := unsafe.Pointer(uintptr(u) + offset)
//
// (4) Conversion of a Pointer to a uintptr when calling syscall.Syscall.
//
// The Syscall functions in package syscall pass their uintptr arguments directly
// to the operating system, which then may, depending on the details of the call,
// reinterpret some of them as pointers.
// That is, the system call implementation is implicitly converting certain arguments
// back from uintptr to pointer.
//
// If a pointer argument must be converted to uintptr for use as an argument,
// that conversion must appear in the call expression itself:
//
//	syscall.Syscall(SYS_READ, uintptr(fd), uintptr(unsafe.Pointer(p)), uintptr(n))
//
// The compiler handles a Pointer converted to a uintptr in the argument list of
// a call to a function implemented in assembly by arranging that the referenced
// allocated object, if any, is retained and not moved until the call completes,
// even though from the types alone it would appear that the object is no longer
// needed during the call.
//
// For the compiler to recognize this pattern,
// the conversion must appear in the argument list:
//
//	// INVALID: uintptr cannot be stored in variable
//	// before implicit conversion back to Pointer during system call.
//	u := uintptr(unsafe.Pointer(p))
//	syscall.Syscall(SYS_READ, uintptr(fd), u, uintptr(n))
//
// (5) Conversion of the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr
// from uintptr to Pointer.
//
// Package reflect's Value methods named Pointer and UnsafeAddr return type uintptr
// instead of unsafe.Pointer to keep callers from changing the result to an arbitrary
// type without first importing "unsafe". However, this means that the result is
// fragile and must be converted to Pointer immediately after making the call,
// in the same expression:
//
//	p := (*int)(unsafe.Pointer(reflect.ValueOf(new(int)).Pointer()))
//
// As in the cases above, it is invalid to store the result before the conversion:
//
//	// INVALID: uintptr cannot be stored in variable
//	// before conversion back to Pointer.
//	u := reflect.ValueOf(new(int)).Pointer()
//	p := (*int)(unsafe.Pointer(u))
//
// (6) Conversion of a reflect.SliceHeader or reflect.StringHeader Data field to or from Pointer.
//
// As in the previous case, the reflect data structures SliceHeader and StringHeader
// declare the field Data as a uintptr to keep callers from changing the result to
// an arbitrary type without first importing "unsafe". However, this means that
// SliceHeader and StringHeader are only valid when interpreting the content
// of an actual slice or string value.
//
//	var s string
//	hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) // case 1
//	hdr.Data = uintptr(unsafe.Pointer(p))              // case 6 (this case)
//	hdr.Len = n
//
// In this usage hdr.Data is really an alternate way to refer to the underlying
// pointer in the string header, not a uintptr variable itself.
//
// In general, reflect.SliceHeader and reflect.StringHeader should be used
// only as *reflect.SliceHeader and *reflect.StringHeader pointing at actual
// slices or strings, never as plain structs.
// A program should not declare or allocate variables of these struct types.
//
//	// INVALID: a directly-declared header will not hold Data as a reference.
//	var hdr reflect.StringHeader
//	hdr.Data = uintptr(unsafe.Pointer(p))
//	hdr.Len = n
//	s := *(*string)(unsafe.Pointer(&hdr)) // p possibly already lost
//
type Pointer *ArbitraryType

unsafe包提供了一些跳过go语言类型安全限制的操作。

1、任意类型的指针可以转换为一个Pointer类型值

unsafe.Pointer(&a)

2、一个Pointer类型值可以转换为任意类型的指针

// 此处将 float64 类型直接转换成 uint64 类型
func Float64bits(f float64) uint64 {
	return *(*uint64)(unsafe.Pointer(&f))
}

// 在标准库经常能看到这个用法,比如 src/runtime/select.go 中 selectgo() 方法里
// cas1 := (*[1 << 16]scase)(unsafe.Pointer(cas0))
// cas0 类型为 *scase,此表达式意为将 cas0 转换成 *[65536]scase
var a int64 = 10
fmt.Printf("%#v\n", a)  // 10
fmt.Printf("%#v\n", &a) // (*int64)(0xc00000a0b8)

c := (*int)(unsafe.Pointer(&a))
fmt.Printf("%#v\n", c)  // (*int)(0xc00000a0b8)
fmt.Printf("%#v\n", *c) // 10

b := (*string)(unsafe.Pointer(&a))
fmt.Printf("%#v\n", b)  // (*string)(0xc00000a0b8)
fmt.Printf("%#v\n", *b) // 报错

// unsafe.Pointer() 将当前地址包装成了任意类型的地址,然后通过类型强制转换得到了特定类型的地址,虽然可以目标类型不做限
// 制,但是只有合适的类型才能完成将内存中的值赋值给变量。

// 需要注意的是,经过 unsafe.Pointer 的转换,其实他们指向的是同一块地址,只不过 a 变量认为它是 int64,而 c 变量认为
// 它是 int 类型的。
var a int64 = 10
c := (*int)(unsafe.Pointer(&a))
*c = 11
fmt.Println(a) // 11


3、一个Pointer类型值可以转换为一个uintptr类型值

uintptr(unsafe.Pointer(&a))

4、一个uintptr类型值可以转换为一个Pointer类型值

uintptr 不能随意的转回 Pointer,可能会破坏类型系统,因为并不是所有的数字都是有效的内存地址。
uintptr 并没有指针的语义,意思就是存储 uintptr 值的内存地址在Go发生GC时会被回收。而 unsafe.Pointer 有指针语义,
可以保护它不会被垃圾回收。所以使用过程中最好不要 Pointer --> uintptr --> Pointer;而只进行 Pointer --> uintptr。

实际上在Go的变量定义中,大量用到 uintptr 来表示地址,比如,字符串的结构 reflect.StringHeader

// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
	Data uintptr
	Len  int
}

再比如切片结构 reflect.SliceHeader

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

所以需要将 uintptr 转换成 unsafe.Pointer 然后最终还原成原先的变量

var a int64 = 10
pt := uintptr(unsafe.Pointer(&a))
b := (*int64)(unsafe.Pointer(pt))
fmt.Printf("%#v\n", b)  // (*int64)(0xc000115f48)
fmt.Printf("%#v\n", *b) // 10

对于复杂的类型,比如结构体

type Admin struct {
	Age  int
	Name string
}

admin := Admin{
	Name: "aaa",
	Age:  10,
}
ptr := &admin
fmt.Printf("%#v\n", ptr) // &main.Admin{Age:10, Name:"aaa"}

// 注意,第一个字段是 Age 而不是 Name
// unsafe.Pointer(ptr) 获取的是第一个字段的指针
age := (*int)(unsafe.Pointer(ptr))
fmt.Printf("%#v\n", age) // (*int)(0xc000004078)
*age = 11
fmt.Printf("%#v\n", ptr) // &main.Admin{Age:11, Name:"aaa"}

// 获取第二个字段的地址
name := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + unsafe.Offsetof(admin.Name)))
fmt.Printf("%#v\n", name) // (*string)(0xc000004080)
*name = "bbb"
fmt.Printf("%#v\n", ptr) // &main.Admin{Age:11, Name:"bbb"}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值