文档章节

Go之unsafe.Pointer && uintptr类型

秋风醉了
 秋风醉了
发布于 2016/08/08 17:20
字数 1309
阅读 573
收藏 0
点赞 0
评论 0

Go之unsafe.Pointer && uintptr类型

unsafe.Pointer

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

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

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

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

通过将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
	return *(*uint64)(unsafe.Pointer(&f))
}

func main() {
	fmt.Printf("%#016x\n", Float64bits(1.0)) // "0x3ff0000000000000"
}

再看一个例子,

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

	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
}

关于unsafe.Pointer的其它用法请参见:http://my.oschina.net/goal/blog/193698

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地址。**第三个向之前无效地址空间的赋值语句将彻底摧毁整个程序!

=========END=========

© 著作权归作者所有

共有 人打赏支持
秋风醉了
粉丝 223
博文 581
码字总数 411013
作品 0
东城
程序员
cgo的一些经验

cgo可以在go语言中夹杂着C函数或数据,在使用cgo时,有一些需要注意的: 1、go中的int/int32/int64/uint32/uint64和C语言中的int/int32等是不同的,因此,C语言的函数的参数不能是go语言的i...

徐学良 ⋅ 2015/12/25 ⋅ 0

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

看了 @喻恒春 大神的利用unsafe.Pointer来突破私有成员,觉得例子举得不太好。而且不应该简单的放个demo,至少要讲一下其中的原理,让看的童鞋明白所以然。see:http://my.oschina.net/achun...

陈亦 ⋅ 2014/01/17 ⋅ 10

原子操作之sync/atomic

原子操作之sync/atomic 对于并发操作而言,原子操作是个非常现实的问题。典型的就是i++的问题。 当两个CPU同时对内存中的i进行读取,然后把加一之后的值放入内存中,可能两次i++的结果,这个...

秋风醉了 ⋅ 2016/08/07 ⋅ 0

Go调用Window SendARP() 方法

首先用了walk里面的一个winapi.go "runtime""syscall""unsafe" runtime.LockOSThread() S_OK = 0x00000000S_FALSE = 0x00000001E_UNEXPECTED = 0x8000FFFFE_NOTIMPL = 0x80004001E_OUTOFMEMO......

独孤小败 ⋅ 2012/10/31 ⋅ 0

Golang sync.Cond源码分析

cond的主要作用就是获取锁之后,wait()方法会等待一个通知,来进行下一步锁释放等操作,以此控制锁合适释放,释放频率,适用于在并发环境下goroutine的等待和通知。 针对Golang 1.9的sync.Co...

梦朝思夕 ⋅ 04/23 ⋅ 0

【go语言】wait,why not safe

slice?俺知道,不就是基于数组的一个视窗嘛! 出个题呗~ 好~ package main func main() { var arr = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s := arr[2:6] modify(s) } func modify(tmp []......

qingkechina ⋅ 2017/02/07 ⋅ 0

Go和C如何共享内存资源

Go语言作为一个现代化的编程语言以及支持垃圾内存的自动回收特性(GC).我们现在关注的是C语言返回的内存资源的自动回收技术. CGO初步 Go语言的cgo技术允许在Go代码中方便的使用C语言代码. 基本...

chai2010 ⋅ 2013/10/13 ⋅ 2

Golang 源码阅读 os.File

最近写程序过程感觉golang读写文件比较慢。因此决定读一下源码。 src/os/file.go http://www.ieyebrain.com:8080/golang/src/os/file.go 中定义了file的函数: Name, Read,Write,Seek,Close等...

shengjuntu ⋅ 2016/11/13 ⋅ 0

Go语言中不同类型切片之间的相互转换

将 切片转换为 类似C语言中将其他类型的数组转换为数组: func ByteSlice(slice interface{}) (data []byte) {sv := reflect.ValueOf(slice)if sv.Kind() != reflect.Slice {panic(fmt.Spri......

chai2010 ⋅ 2014/05/20 ⋅ 2

golang Reflect包

Reflect包 Reflect 反射包有2个重要的类型,分别通过和返回。分别在源码包里的包中的和 Type TypeOf() 返回一个接口类型,源码中 有一个结构体 实现了接口的所有方法。源码: TypeOf会返回一...

johnL ⋅ 03/05 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部