• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

unsafe非类型安全指针Uintanduintptringolang非类型安全指针你不知道的Gounsafe.Point ...

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 


小结
1、
概念 转换

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.
Converting a Pointer to a uintptr produces the memory address of the value pointed at, as an integer.


a Pointer----> <----a pointer value of any type
a Pointer----> <----a uintptr

1.1.2
a Pointer 1---->a uintptr----> a Pointer 2

应用:
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]))

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.

将指针转化为uintptr,再加上偏移量,最后转换回指针:可以用来访问结构体的字段或数组的元素

注意还有其他用法,详情请见 源码 src\unsafe\unsafe.go

 

 

 

 

unsafe\unsafe.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
	Package unsafe contains operations that step around the type safety of Go programs.

	Packages that import unsafe may be non-portable and are not protected by the
	Go 1 compatibility guidelines.
*/
package unsafe

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

// IntegerType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents any arbitrary integer type.
type IntegerType int

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

// 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.
// The return value of Sizeof is a Go constant.
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.
// The return value of Offsetof is a Go constant.
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().
// The return value of Alignof is a Go constant.
func Alignof(x ArbitraryType) uintptr

// The function Add adds len to ptr and returns the updated pointer
// Pointer(uintptr(ptr) + uintptr(len)).
// The len argument must be of integer type or an untyped constant.
// A constant len argument must be representable by a value of type int;
// if it is an untyped constant it is given type int.
// The rules for valid uses of Pointer still apply.
func Add(ptr Pointer, len IntegerType) Pointer

// The function Slice returns a slice whose underlying array starts at ptr
// and whose length and capacity are len.
// Slice(ptr, len) is equivalent to
//
//	(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
//
// except that, as a special case, if ptr is nil and len is zero,
// Slice returns nil.
//
// The len argument must be of integer type or an untyped constant.
// A constant len argument must be non-negative and representable by a value of type int;
// if it is an untyped constant it is given type int.
// At run time, if len is negative, or if ptr is nil and len is not zero,
// a run-time panic occurs.
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

  

 math\unsafe.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package math

import "unsafe"

// Float32bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position.
// Float32bits(Float32frombits(x)) == x.
func Float32bits(f float32) uint32 { return *(*uint32)(unsafe.Pointer(&f)) }

// Float32frombits returns the floating-point number corresponding
// to the IEEE 754 binary representation b, with the sign bit of b
// and the result in the same bit position.
// Float32frombits(Float32bits(x)) == x.
func Float32frombits(b uint32) float32 { return *(*float32)(unsafe.Pointer(&b)) }

// Float64bits returns the IEEE 754 binary representation of f,
// with the sign bit of f and the result in the same bit position,
// and Float64bits(Float64frombits(x)) == x.
func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }

// Float64frombits returns the floating-point number corresponding
// to the IEEE 754 binary representation b, with the sign bit of b
// and the result in the same bit position.
// Float64frombits(Float64bits(x)) == x.
func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) }

  

 

package main

import (
	"fmt"
	"unsafe"
)

func f() {
	a := [16]int{3: 3, 9: 9, 11: 11}

	eleSize := int(unsafe.Sizeof(a[0]))
	p9 := &a[9]
	up9 := unsafe.Pointer(p9)
	p3 := (*int)(unsafe.Add(up9, -6*eleSize))

	s := unsafe.Slice(p9, 5)[:3]

	t := unsafe.Slice((*int)(nil), 0)

	_ = unsafe.Add(up9, 7*eleSize)
	_ = unsafe.Slice(p9, 8)
	fmt.Println(a)
	fmt.Println(*p3)
	fmt.Println(s)
	fmt.Println(len(s), cap(s))
	fmt.Println(t == nil)
	i := unsafe.Add(up9, 7*eleSize)
	j := unsafe.Add(up9, 16*eleSize)
	i1 := unsafe.Add(up9, 1*eleSize)
	j1 := unsafe.Add(up9, 2*eleSize)
	j3 := unsafe.Add(up9, 17*eleSize)
	fmt.Println(i, j)
	fmt.Println(i1, j1, j3)
}

func main() {

	f()

}

  

[0 0 0 3 0 0 0 0 0 9 0 11 0 0 0 0]
3
[9 0 11]
3 5
true
0xc0000d4100 0xc0000d4148
0xc0000d40d0 0xc0000d40d8 0xc0000d4150

unsafe package - unsafe - pkg.go.dev https://pkg.go.dev/unsafe

https://pkg.go.dev/unsafe

 非类型安全指针 - Go语言101(通俗版Go白皮书) https://gfw.go101.org/article/unsafe.html

 

 

(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))
}

 

 

 

 

Understanding uintptr in Golang - Welcome To Golang By Example https://golangbyexample.com/understanding-uintptr-golang/

This is an unsigned integer type which is large enough to hold any pointer address. Therefore its size is platform dependent. It is just an integer representation of an address.

 

Properties

  • A uintptr can be converted to unsafe.Pointer and viceversa. Later we will talk about where conversion of uintptr to unsafe.Pointer is useful.
  • Arithmetic can be performed on the uintptr. Do note here arithmetic cannot be performed in a pointer in Go or unsafe.Pointer in Go.
  • uintptr even though it holds a pointer address, is just a value and does not reference any object. Therefore
    • Its value will not be updated if the corresponding object moves. Eg When goroutine stack changes
    • The corresponding object can be garbage collected. The GC does not consider uintptr as live references and hence they can be garbage collected.

 

Purpose

uintptr can be used for below purposes:

  • One purpose of uintptr is to be used along with unsafe.Pointer for unsafe memory access. Arithmetic operations cannot be performed on unsafe.Pointer. To perform such arithmetic
    • unsafe. Pointer is converted to uintptr
    • arithmetic is then performed on uintptr
    • uintptr is converted back to unsafe.Pointer  to access the object now pointed by the address

Be careful that the above steps should be atomic with respect to Garbage Collector, otherwise it could lead to issues. For eg after the first step 1, the referenced object is liable to be collection. If that happens then after step 3, the pointer will be an invalid Go pointer and can crash the program. Look at the unsafe package documentation.

https://golang.org/pkg/unsafe/#Pointer

It lists down when the above conversion can be safe. See the below code for the scenario mentioned above.

In the below code we are doing arithmetic like below to get to address of field “b” in struct sample and then printing the value at that address. This below code is atomic with reference to the garbage collector.

p := unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Offsetof(s.b))
package main
import (
    "fmt"
    "unsafe"
)
type sample struct {
    a int
    b string
}
func main() {
    s := &sample{a: 1, b: "test"}
    
   //Getting the address of field b in struct s
    p := unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Offsetof(s.b))
    
    //Typecasting it to a string pointer and printing the value of it
    fmt.Println(*(*string)(p))
}

Output:

test
  • Another purpose of uintptr is when you want to save the pointer address value for printing it or storing it. Since the address is just stored and does not reference anything, the corresponding object can be garbage collected.

See below code where we are converting an unsafe.Pointer to uintptr and printing it. Also, note as mentioned before too one the unsafe.Pointer is converted to uinptr, the reference is lost and the reference variable can be garbage collected.

package main

import (
    "fmt"
    "unsafe"
)

type sample struct {
    a int
    b string
}

func main() {
    s := &sample{
        a: 1,
        b: "test",
    }
    //Get the address as a uintptr
    startAddress := uintptr(unsafe.Pointer(s))
    fmt.Printf("Start Address of s: %d\n", startAddress)
}

Output:

The output will be dependent upon the machine as it is an address.

Start Address of s: 824634330992

 atomic.LoadUintptr() Function in Golang With Examples - GeeksforGeeks https://www.geeksforgeeks.org/atomic-loaduintptr-function-in-golang-with-examples/

 

In Go language, atomic packages supply lower-level atomic memory that is helpful is implementing synchronization algorithms. The LoadUintptr() function in Go language is used to atomically load *addr. This function is defined under the atomic package. Here, you need to import “sync/atomic” package in order to use these functions.

Syntax:

func LoadUintptr(addr *uintptr) (val uintptr)

Here, addr indicates address.

Note: (*uintptr) is the pointer to a uintptr value. And uintptr is an integer type that is too large that it can contain the bit pattern of any pointer.

Return value: It returns the value loaded to the *addr.

 

 
 

 

Example 1:

 
// Program to illustrate the usage of
// LoadUintptr function in Golang
  
// Including main package
package main
  
// importing fmt and sync/atomic
import (
    "fmt"
    "sync/atomic"
)
  
// Main function
func main() {
  
    // Assigning values
    // to the uintptr
    var (
        i uintptr = 98
        j uintptr = 255
        k uintptr = 6576567667788
        l uintptr = 5
    )
  
    // Calling LoadUintptr method
    // with its parameters
    load_1 := atomic.LoadUintptr(&i)
    load_2 := atomic.LoadUintptr(&j)
    load_3 := atomic.LoadUintptr(&k)
    load_4 := atomic.LoadUintptr(&l)
  
    // Displays uintptr value
    // loaded in the *addr
    fmt.Println(load_1)
    fmt.Println(load_2)
    fmt.Println(load_3)
    fmt.Println(load_4)
}

Output:

98
255
6576567667788
5

Example 2:

 
// Program to illustrate the usage of
// LoadUintptr function in Golang
  
// Including main package
package main
  
// Importing fmt and sync/atomic
import (
    "fmt"
    "sync/atomic"
)
  
// Main function
func main() {
  
    // Declaring u
    var u uintptr
  
    // For loop
    for i := 1; i < 1000; i += 1 {
  
        // Function with
        // AddUintptr method
        go func() {
            atomic.AddUintptr(&u, 9)
        }()
    }
  
    // Prints loaded values address
    fmt.Println(atomic.LoadUintptr(&u))
}

Output:

1818   // A random value is returned in each run

In the above example, the new values are returned from AddUintptr() method in each call until the loop stops, LoadUintptr() method loads these new uintptr values. And these values are stored in different addresses which can be random one so, the output of the LoadUintptr() method here in each run is different. So, here a random value is returned in the output.

 

go - Uint and uintptr in golang - Stack Overflow https://stackoverflow.com/questions/69182402/uint-and-uintptr-in-golang

 非类型安全指针 - Go语言101(通俗版Go白皮书) https://gfw.go101.org/article/unsafe.html

 unsafe - The Go Programming Language https://golang.google.cn/pkg/unsafe/#Pointer

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

Add

func Add(ptr Pointer, len IntegerType) Pointer

The function Add adds len to ptr and returns the updated pointer Pointer(uintptr(ptr) + uintptr(len)). The len argument must be of integer type or an untyped constant. A constant len argument must be representable by a value of type int; if it is an untyped constant it is given type int. The rules for valid uses of Pointer still apply.

 

非类型安全指针

我们已经从Go中的指针一文中学习到关于指针的各种概念和规则。 从那篇文章中,我们得知,相对于C指针,Go指针有很多限制。 比如,Go指针不支持算术运算,并且对于任意两个指针值,很可能它们不能转换到对方的类型。

事实上,在那篇文章中解释的指针的完整称呼应该为类型安全指针。 虽然类型安全指针有助于我们轻松写出安全的代码,但是有时候施加在类型安全指针上的限制也确实导致我们不能写出最高效的代码。

实际上,Go也支持限制较少的非类型安全指针。 非类型安全指针和C指针类似,它们都很强大,但同时也都很危险。 在某些情形下,通过非类型安全指针的帮助,我们可以写出效率更高的代码; 但另一方面,使用非类型安全指针也导致我们可能轻易地写出潜在的不安全的代码,这些潜在的不安全点很难在它们产生危害之前被及时发现。

使用非类型安全指针的另外一个较大的风险是Go中目前提供的非类型安全指针机制并不受到Go 1 兼容性保证的保护。 使用了非类型安全指针的代码可能从今后的某个Go版本开始将不再能编译通过,或者运行行为发生了变化。

如果出于种种原因,你确实希望在你的代码中使用非类型安全指针,你不仅需要提防上述风险,你还需遵守Go官方文档中列出的非类型安全指针使用模式,并清楚地知晓使用非类型安全指针带来的效果。否则,你很难使用非类型安全指针写出安全的代码。

关于unsafe标准库包

非类型安全指针在Go中为一种特别的类型。 我们必须引入unsafe标准库包来使用非类型安全指针。 非类型安全指针unsafe.Pointer被声明定义为:
type Pointer *ArbitraryType

当然,这不是一个普通的类型定义。这里的ArbitraryType仅仅是暗示unsafe.Pointer类型值可以被转换为任意类型安全指针(反之亦然)。换句话说,unsafe.Pointer类似于C语言中的void*

非类型安全指针是指底层类型为unsafe.Pointer的类型。

非类型安全指针的零值也使用预声明的nil标识符来表示。

在Go 1.17之前,unsafe标准库包只提供了三个函数:
  • func Alignof(variable ArbitraryType) uintptr。 此函数用来取得一个值在内存中的地址对齐保证(address alignment guarantee)。 注意,同一个类型的值做为结构体字段和非结构体字段时地址对齐保证可能是不同的。 当然,这和具体编译器的实现有关。对于目前的标准编译器,同一个类型的值做为结构体字段和非结构体字段时的地址对齐保证总是相同的。 gccgo编译器对这两种情形是区别对待的。
  • func Offsetof(selector ArbitraryType) uintptr。 此函数用来取得一个结构体值的某个字段的地址相对于此结构体值的地址的偏移。 在一个程序中,对于同一个结构体类型的不同值的对应相同字段,此函数的返回值总是相同的。
  • func Sizeof(variable ArbitraryType) uintptr。 此函数用来取得一个值的尺寸(亦即此值的类型的尺寸)。 在一个程序中,对于同一个类型的不同值,此函数的返回值总是相同的。
注意:
  • 这三个函数的返回值的类型均为内置类型uintptr。下面我们将了解到uintptr类型的值可以转换为非类型安全指针(反之亦然)。
  • 尽管这三个函数之一的任何调用的返回结果在同一个编译好的程序中总是一致的,但是这样的一个调用在不同架构的操作系统中(或者使用不同的编译器编译时)的返回值可能是不一样的。
  • 这三个函数的调用总是在编译时刻被估值,估值结果为类型为uintptr的常量。
  • 传递给Offsetof函数的实参必须为一个字段选择器形式value.field。 此选择器可以表示一个内嵌字段,但此选择器的路径中不能包含指针类型的隐式字段
一个使用了这三个函数的例子:
package main

import "fmt"
import "unsafe"

func main() {
	var x struct {
		a int64
		b bool
		c string
	}
	const M, N = unsafe.Sizeof(x.c), unsafe.Sizeof(x)
	fmt.Println(M, N) // 16 32

	fmt.Println(unsafe.Alignof(x.a)) // 8
	fmt.Println(unsafe.Alignof(x.b)) // 1
	fmt.Println(unsafe.Alignof(x.c)) // 8

	fmt.Println(unsafe.Offsetof(x.a)) // 0
	fmt.Println(unsafe.Offsetof(x.b)) // 8
	fmt.Println(unsafe.Offsetof(x.c)) // 16
}

 

下面是一个展示了上面提到的最后一个注意点的例子:
package main

import "fmt"
import "unsafe"

func main() {
	type T struct {
		c string
	}
	type S struct {
		b bool
	}
	var x struct {
		a int64
		*S
		T
	}

	fmt.Println(unsafe.Offsetof(x.a)) // 0
	
	fmt.Println(unsafe.Offsetof(x.S)) // 8
	fmt.Println(unsafe.Offsetof(x.T)) // 16
	
	// 此行可以编译过,因为选择器x.c中的隐含字段T为非指针。
	fmt.Println(unsafe.Offsetof(x.c)) // 16
	
	// 此行编译不过,因为选择器x.b中的隐含字段S为指针。
	//fmt.Println(unsafe.Offsetof(x.b)) // error
	
	// 此行可以编译过,但是它将打印出字段b在x.S中的偏移量.
	fmt.Println(unsafe.Offsetof(x.S.b)) // 0
}

注意,上面程序中的注释所暗示的输出结果是此程序在AMD64架构上使用标准编译器1.17版本编译时的结果。

unsafe包提供的这三个函数看上去并不怎么危险。 它们的原型在以后的Go 1版本中几乎不可能会发生改变。 Rob Pike甚至曾经将这几个函数挪到其它包中。 unsafe包的危险性基本上来自于非类型安全指针。它们和C指针一样危险,这是Go安全指针千方百计设法去避免的。

Go 1.17引入了一个新类型和两个新函数。 此新类型为IntegerType。它的定义如下。 此类型不代表着一个具体类型,它只是表示任意整数类型(有点泛型的意思)。
type IntegerType int
Go 1.17引入的两个函数为:
  • func Add(ptr Pointer, len IntegerType) Pointer。 此函数在一个(非安全)指针表示的地址上添加一个偏移量,然后返回表示新地址的一个指针。 此函数以一种更正规的形式部分地覆盖了下面将要介绍的使用模式3中展示的合法用法。
  • func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType。 此函数用来从一个任意(安全)指针派生出一个指定长度的切片。
Go 1.17引入的这两个函数具有一定的危险性,需谨慎使用。下面时使用了这两个函数的一个例子。
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	a := [16]int{3: 3, 9: 9, 11: 11}
	fmt.Println(a)
	eleSize := int(unsafe.Sizeof(a[0]))
	p9 := &a[9]
	up9 := unsafe.Pointer(p9)
	p3 := (*int)(unsafe.Add(up9, -6 * eleSize))
	fmt.Println(*p3) // 3
	s := unsafe.Slice(p9, 5)[:3]
	fmt.Println(s) // [9 0 11]
	fmt.Println(len(s), cap(s)) // 3 5

	t := unsafe.Slice((*int)(nil), 0)
	fmt.Println(t == nil) // true

	// 下面是两个不正确的调用。因为它们
	// 的返回结果引用了未知的内存块。
	_ = unsafe.Add(up9, 7 * eleSize)
	_ = unsafe.Slice(p9, 8)
}

非类型安全指针相关的类型转换

目前(Go 1.17),Go支持下列和非类型安全指针相关的类型转换:
  • 一个类型安全指针值可以被显式转换为一个非类型安全指针类型,反之亦然。
  • 一个uintptr值可以被显式转换为一个非类型安全指针类型,反之亦然。 但是,注意,一个nil非类型安全指针类型不应该被转换为uintptr并进行算术运算后再转换回来。

通过使用这些转换规则,我们可以将任意两个类型安全指针转换为对方的类型,我们也可以将一个安全指针值和一个uintptr值转换为对方的类型。

然而,尽管这些转换在编译时刻是合法的,但是它们中一些在运行时刻并非是合法和安全的。 这些转换摧毁了Go的类型系统(不包括非类型安全指针部分)精心设立的内存安全屏障。 我们必须遵循本文后面要介绍的一些用法指示来使用非类型安全指针才能写出合法并安全的代码。

我们需要知道的一些事实

在开始介绍合法的非类型安全指针使用模式之前,我们需要知道一些事实。

事实一:非类型安全指针值是指针但uintptr值是整数

每一个非零安全或者不安全指针值均引用着另一个值。但是一个uintptr值并不引用任何值,它被看作是一个整数,尽管常常它存储的是一个地址的数字表示。

Go是一门支持垃圾回收的语言。 当一个Go程序在运行中,Go运行时(runtime)将不时地检查哪些内存块将不再被程序中的任何仍在使用中的值所引用并且回收这些内存块。 指针在这一过程中扮演着重要的角色。值与值之间和内存块与值之间的引用关系是通过指针来表征的。

既然一个uintptr值是一个整数,那么它可


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap