Go语言是个强类型语言。Go语言要求所有统一表达式的不同的类型之间必须做显示的类型转换。而作为Go语言鼻祖的C语言是可以直接做隐式的类型转换的。

也就是说Go对类型要求严格,不同类型不能进行赋值操作。指针也是具有明确类型的对象,进行严格类型检查。不过Go语言也有例外在一些特殊类型存在隐式转换。

unsafe.Pointer


这个类型比较重要,它是实现定位欲读写的内存的基础。官方文档对该类型有四个重要描述:

(1)任何类型的指针都可以被转化为Pointer
(2)Pointer可以被转化为任何类型的指针
(3)uintptr可以被转化为Pointer
(4)Pointer可以被转化为uintptr

大多数指针类型会写成T,表示是“一个指向T类型变量的指针”。unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void类型的指针),它可以包含任意类型变量的地址。当然,我们不可以直接通过*p来获取unsafe.Pointer指针指向的真实变量的值,因为我们并不知道变量的具体类型。和普通指针一样,unsafe.Pointer指针也是可以比较的,并且支持和nil常量比较判断是否为空指针。

什么叫 unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void类型的指针),它可以包含任意类型变量的地址。??

看句中,我们将句子拆分,可以得到一点,就是 unsafe.Pointer 转换的变量,该变量一定要是指针类型,否则编译会报错。如:

a := 1
b := unsafe.Pointer(a) //报错
b := unsafe.Pointer(&a)

 一个普通的T类型指针可以被转化为unsafe.Pointer类型指针,并且一个unsafe.Pointer类型指针也可以被转回普通的指针,被转回普通的指针类型并不需要和原始的T类型相同。

说明:

  1. unsafe.Pointer 转换的变量类型,一定是指针类型;
  2. & 取址,* 取值;

例1:通过将float64类型指针转化为uint64类型指针,我们可以查看一个浮点数变量的位模式。

package main

import (
"fmt"
"unsafe"
"reflect"
) func Float64bits(f float64) uint64 {
fmt.Println(reflect.TypeOf(unsafe.Pointer(&f))) //unsafe.Pointer
fmt.Println(reflect.TypeOf((*uint64)(unsafe.Pointer(&f)))) //*uint64
     //(*uint64)(&f) //这种类型转换语法是无效的
return *(*uint64)(unsafe.Pointer(&f)) //在变量前加*,是取值
} func main() {
fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}

  

代码  *(*uint64)(unsafe.Pointer(&f)) 我们进行分解:
*(*uint64)(unsafe.Pointer(&f)) 分解:

1. unsafe.Pointer(&f) 转换变量指针f成Pointer类型

2. (*uint64)(unsafe.Pointer(&f))  将上述的Pointer类型转成uint6l的指针类型

3. 在2的结果(指针类型)前加上*,就是获取该指针的变量值

总结: 要获取指针类型的变量的值,只需要在变量前加 * 即可。

例子2:

package main

import (
"fmt"
"reflect"
) func main() { v1 := uint(12)
v2 := int(12) fmt.Println(reflect.TypeOf(v1)) //uint
fmt.Println(reflect.TypeOf(v2)) //int fmt.Println(reflect.TypeOf(&v1)) //*uint
fmt.Println(reflect.TypeOf(&v2)) //*int p := &v1 //两个变量的类型不同,不能赋值
p = &v2 //报错,cannot use &v2 (type *int) as type *uint in assignment,类型不同,不可赋值
        p = (*uint)(&v2)  //这种类型转换语法也是无效的
	fmt.Println(reflect.TypeOf(p)) // *unit
}
当再次把 v2 的指针赋值给p时,会发生错误cannot use &v2 (type *int) as type *uint in assignment,也就是说类型不同,一个是*int,一个是*uint。
这时,可以使用unsafe.Pointer进行转换,如下,
package main

import (
"fmt"
"reflect"
"unsafe"
) func main() { v1 := uint(12)
v2 := int(13) fmt.Println(reflect.TypeOf(v1)) //uint
fmt.Println(reflect.TypeOf(v2)) //int fmt.Println(reflect.TypeOf(&v1)) //*uint
fmt.Println(reflect.TypeOf(&v2)) //*int p := &v1 p = (*uint)(unsafe.Pointer(&v2)) //使用unsafe.Pointer进行类型的转换 fmt.Println(reflect.TypeOf(p)) // *unit
fmt.Println(*p) //13
}

  


uintptr



// uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
type uintptr uintptr

 

uintptr是golang的内置类型,是能存储指针的整型,在64位平台上底层的数据类型是,

typedef unsigned long long int  uint64;
typedef uint64 uintptr;

  

一个unsafe.Pointer指针也可以被转化为uintptr类型,然后保存到指针型数值变量中(注:这只是和当前指针相同的一个数字值,并不是一个指针),然后用以做必要的指针数值运算。(uintptr是一个无符号的整型数,足以保存一个地址)**这种转换虽然也是可逆的,但是将uintptr转为unsafe.Pointer指针可能会破坏类型系统,因为并不是所有的数字都是有效的内存地址。

许多将unsafe.Pointer指针转为原生数字,然后再转回为unsafe.Pointer类型指针的操作也是不安全的。比如下面的例子需要将变量x的地址加上b字段地址偏移量转化为*int16类型指针,然后通过该指针更新x.b:

package main

import (
"fmt"
"unsafe"
) func main() { var x struct {
a bool
b int16
c []int
} /**
unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞.
*/ /**
uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
指针的运算
*/
// 和 pb := &x.b 等价
pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
*pb = 42
fmt.Println(x.b) // "42"
}

  

上面的写法尽管很繁琐,但在这里并不是一件坏事,因为这些功能应该很谨慎地使用。不要试图引入一个uintptr类型的临时变量,因为它可能会破坏代码的安全性(注:这是真正可以体会unsafe包为何不安全的例子)。

下面段代码是错误的

// NOTE: subtly incorrect!
tmp := uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42

  

  产生错误的原因很微妙。**有时候垃圾回收器会移动一些变量以降低内存碎片等问题。这类垃圾回收器被称为移动GC。当一个变量被移动,所有的保存改变量旧地址的指针必须同时被更新为变量移动后的新地址。从垃圾收集器的视角来看,一个unsafe.Pointer是一个指向变量的指针,因此当变量被移动是对应的指针也必须被更新;但是uintptr类型的临时变量只是一个普通的数字,所以其值不应该被改变。上面错误的代码因为引入一个非指针的临时变量tmp,导致垃圾收集器无法正确识别这个是一个指向变量x的指针。当第二个语句执行时,变量x可能已经被转移,这时候临时变量tmp也就不再是现在的&x.b地址。**第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!

Go之unsafe.Pointer && uintptr 类型的更多相关文章

  1. 你不知道的Go unsafe.Pointer uintptr原理和玩法

    unsafe.Pointer 这个类型比较重要,它是实现定位和读写的内存的基础,Go runtime大量使用它.官方文档对该类型有四个重要描述: (1)任何类型的指针都可以被转化为Pointer (2 ...

  2. golang unsafe.Pointer与uintptr

    原文地址:https://blog.fanscore.cn/p/33/ 先说结论 uintptr 是一个地址数值,它不是指针,与地址上的对象没有引用关系,垃圾回收器不会因为有一个uintptr类型的值 ...

  3. Go语言阅读小笔记,来自知呼达达关于unsafe.Pointer的分享.

    第一式 - 获得Slice和String的内存数据 func stringPointer(s string) unsafe.Pointer { p := (*reflect.StringHeader) ...

  4. 使用unsafe.Pointer将结构体转为[]byte

    package main import ( "fmt" "unsafe" ) type TestStructTobytes struct { data int6 ...

  5. 直接修改托管堆栈中的type object pointer(类型对象指针)

    都知道.NET是一个强对象类型的框架. 那么对于对象类型又是怎么确定的呢. 最初的我简单认为数据的类型就是定义时字段的类型修饰决定的(回来发现这种观点是绝对错误的) 我们知道引用对象存储在托管堆栈中, ...

  6. Go中的unsafe

    unsafe 最近关注了一个大佬的文章,文章写的非常好,大家可以去关注下. 微信公众号[码农桃花源] 指针类型 我们知道slice 和 map 包含指向底层数据的指针 什么是 unsafe 为什么会有 ...

  7. [转]Go里面的unsafe包详解

    Golang的unsafe包是一个很特殊的包. 为什么这样说呢? 本文将详细解释. 来自go语言官方文档的警告 unsafe包的文档是这么说的: 导入unsafe的软件包可能不可移植,并且不受Go 1 ...

  8. golang: 利用unsafe操作未导出变量

    unsafe.Pointer其实就是类似C的void *,在golang中是用于各种指针相互转换的桥梁.uintptr是golang的内置类型,是能存储指针的整型,uintptr的底层类型是int,它 ...

  9. Go语言之unsafe包介绍及使用

    unsafe内容介绍 type ArbitraryType int type Pointer *ArbitraryType func Sizeof(x ArbitraryType) uintptr f ...

随机推荐

  1. FastAdmin笔记~

    在phpstud命令行输入以下命令! 1.在命令行一键生成test表格的crud php think crud -t  test 2.在数据库创建表必须加上表注释 3.生成test控制器的权限菜单 p ...

  2. vue--数据显示模版上

    首先需要知道 挂载点:是index.html文件下的一个dom节点 模板:挂载点里的内容,也就是模板内容. 组件: 页面上的某一部分内容.当我们的项目比较大时,可以将页面拆分成几个部分,每个部分就是一 ...

  3. P1122 最大子树和

    传送门 思路: 任意找一个点为树根.DFS 遍历树,如果子树和为负就直接跳过,不然就统计进答案.( 虽是任意取一点为根,但不一定从这个点出发能够取得最优解,要开一个 ans 记录一下最大值.) 标程: ...

  4. 文件上传下载显示进度(vue)

    编写了一个vue组件,可以实时显示文件上传和下载时候的进度 <template> <div v-show="circleProgress_wrapper_panel_sta ...

  5. Matlab:椭圆方程的导数边值问题

    tic; clear clc N=; M=*N; h1=/M; h2=/N; x=:h1:; y=:h2:; fun=inline('exp(x)*sin(pi*y)','x','y'); f=inl ...

  6. js验证银行卡号 luhn校验规则

    <script type="text/javascript"> //luhn校验规则:16位银行卡号(19位通用): // 1.将未带校验位的 15(或18)位卡号从右 ...

  7. Django框架(六)

    十一.Django组件-cookie与session 1.会话跟踪技术 (1) 什么是会话跟踪技术 我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多 ...

  8. linux 使用split分割大文件

    1.分割 -- split命令 可以指定按行数分割和按字节大小分割两种模式. (1) 按行数分割 $ split -l 300 large_file.txt new_file_prefix 加上-d, ...

  9. Python—集合的操作、文件的操作

    1.集合的操作 2.文件的操作 1.集合的操作 定义: 1.不同元素组成,自动去重 2.无序 3.集合中的元素必须是不可变类型 1.集合的定义: >>> s1 = set('abcd ...

  10. DMSkin for WPF 开源在Github

    DMSkin for WPF 开源在Github http://www.dmskin.com/