前言

我们知道在golang中是存在指针这个概念的。对于指针很多人有点忌惮(可能是因为之前学习过C语言),因为它会导致很多异常的问题。但是很多人学习之后发现,golang中的指针很简单,没有C那么复杂。所以今天就详细来说说指针。

因为博客园发布markdown格式存在问题,请移步http://www.linkinstar.wiki/2019/06/06/golang/source-code/point-unsafe/

指针的使用

a := 1
p := &a
fmt.Println(p)

输出:0xc42001c070

可以看到p就是一个指针,也可以说是a的地址。

a := 1
var p *int
p = &a
fmt.Println(p)

或者也可以写成这样,因为我知道,在很多人看来,看到*号才是指针(手动滑稽)

a := 1
p := &a
fmt.Println(*p)

输出:1

然后使用就直接通过*号就能去到对应的值了,就这么简单

指针的限制

Golang中指针之所以看起来很简单,是因为指针的功能不多。我们能看到的功能就是指针的指向一个地址而已,然后对于这个地址也只能进行传递,或者通过这个的地址去访问值。

  • 不能像C语言中一样p++,这样移动操作指针,因为其实这样操作确实不安全,很容易访问到奇怪的区域。
  • 不同类型的指针不能相互赋值、转换、比较。会出现cannot use &a (type *int) as type *float32 in assignment类似这样的错误

如果只是单纯说go中指针的功能,上面就已经说完了,没必要写博客,但是其实go中还有一个包叫unsafe,有了它,指针就可以像C一样想干嘛干嘛了。

unsafe

三个类型

其实指针有三种:
一种是我们常见的*,用*去表示的指针;
一种是unsafe.Pointer,Pointer是unsafe包下的一个类型;
最后一种是uintptr,uintptr就厉害了,这玩意是可以进行运算的也就是可以++--;

他们之间有这样的转换关系:
* <=> unsafe.Pointer <=> uintptr

  • 有一点要注意的是,uintptr 并没有指针的语义,意思就是 uintptr 所指向的对象会被 gc 无情地回收。而 unsafe.Pointer 有指针语义,可以保护它所指向的对象在“有用”的时候不会被垃圾回收。

从这样的关系你大概就可以猜到,我们使用的指针*p转换成Pointer然后转换uintptr进行运算之后再原路返回,理论上就能等同于进行了指针的运算。我们下面就来实践一下。

unsafe操作slice

func main() {
s := make([]int, 10)
s[1] = 2 p := &s[0]
fmt.Println(*p) up := uintptr(unsafe.Pointer(p))
up += unsafe.Sizeof(int(0)) // 这里可不是up++哦 p2 := (*int)(unsafe.Pointer(up))
fmt.Println(*p2)
}

输出:
0
2

从代码中我们可以看到,我们首先将指针指向切片的第一个位置,然后通过转换得到uintptr,操作uintptr + 上8位(注意这里不能++因为存放的是int,下一个元素位置相隔举例int个字节),最后转换回来得到指针,取值,就能取到切片的第二个位置了。

unsafe操作struct

当然有人肯定要说了,上面那个一顿操作猛如虎,不就是访问下一个位置嘛,我直接访问就行了。
那下面就是厉害的来了,我们知道如果一个结构体里面定义的属性是私有的,那么这个属性是不能被外界访问到的。我们来看看下面这个操作:

package basic

type User struct {
age int
name string
}
package main

func main() {
user := &basic.User{}
fmt.Println(user) s := (*int)(unsafe.Pointer(user))
*s = 10 up := uintptr(unsafe.Pointer(user)) + unsafe.Sizeof(int(0)) namep := (*string)(unsafe.Pointer(up))
*namep = "xxx" fmt.Println(user)
}

User是另外一个basic包中的结构体,其中的age是小写开头的,理论上来说,我们在外部没有办法修改age的值,但是经过上面这波操作之后,输出信息是:
&{0 }
&{10 xxx}
也就是说成功操作到了结构体的私有属性。

顺便提一句:创建结构体会被分配一块连续的内存,结构体的地址也代表了第一个成员的地址。

下面我们来验证一下你是否已经学会了unsafe的操作,尝试不看一个小结,自己尝试一下:如何完成字符串到[]byte的转换,并且不开辟新的空间?

字符串和byte数组转换inplace

我们知道如果将字符串转换成[]byte非常方便

s := "123"
a := []byte(s)

但是这样需要开辟额外的空间,那么如何实现原地的,不需要拷贝数据的转换呢?
我们想一下,其实从底层的存储角度来说,string的存储规则和[]byte是一样的,也就是说,其实指针都是从某个位置开始到一段空间,中间一格一格。所以利用unsafe就可以做到。

func main() {
s := "123"
a := []byte(s) print("s = " , &s, "\n")
print("a = " , &a, "\n") a2 := (*[]byte)(unsafe.Pointer(&s))
print("a2 = " , a2, "\n") fmt.Println(*a2)
}

输出结果:
s = 0xc420055f40
a = 0xc420055f60
a2 = 0xc420055f40
[49 50 51]

我们可以看到s和a的地址是不一样的,但是s和a2的地址是一样的,并且a2已经是一个[]byte了。

嘿嘿嘿~你以为这样就结束了???

存在的问题

其实这个转换是存在问题的,问题就在新的[]byte的Cap没有正确的初始化。
我们打印一下cap看一下
fmt.Println("cap a =", cap(a))
fmt.Println("cap a2 =", cap(*a2))
结果是:
cap a = 32
cap a2 = 17418400
这么大的容量是要上天呢???

问题的原因

在src/reflect/value.go下看

type StringHeader struct {
Data uintptr
Len int
} type SliceHeader struct {
Data uintptr
Len int
Cap int
}

看到其实string没有cap而[]byte有,所以导致问题出现,也容易理解,string是没有容量扩容这个说法的,所以新的[]byte没有赋值cap所以使用了默认值。

问题解决

stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))

bh := reflect.SliceHeader{
Data: stringHeader.Data,
Len: stringHeader.Len,
Cap: stringHeader.Len,
} return *(*[]byte)(unsafe.Pointer(&bh))

通过重新设置SliceHeader就可以完成

总结

以上就是所有golang指针和unsafe的相关细节和使用。那么肯定有人会问这个有什么用了?

  • 1、没啥事你就别乱用了,别人都说unsafe不安全了。
  • 2、源码中很多大量的使用了指针移动的操作。

如map中通过key获取value的时候:

v := add(unsafe.Pointer(b), dataOffset+bucketCnt * uintptr(t.keysize)+i * uintptr(t.valuesize))

通过桶的指针的偏移拿到值,具体我就不多介绍了。
总之对于你看golang源码的时候会有很大帮助的。可能必要的时候你也能用到它,还是那句话,除非你知道它在干什么,否则不要用。

Golang指针与unsafe的更多相关文章

  1. TODO:Golang指针使用注意事项

    TODO:Golang指针使用注意事项 先来看简单的例子1: 输出: 1 1 例子2: 输出: 1 3 例子1是使用值传递,Add方法不会做任何改变:例子2是使用指针传递,会改变地址,从而改变地址. ...

  2. golang中,unsafe.sizeof到底是干嘛的?

    https://www.golangtc.com/t/5ad833404ce40d2654053485 小生初学Go,有一点不懂,今天为了知道空结构体到底占多大的空间的时候,去百度说用unsafe.s ...

  3. 【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  4. Unity3d中C#使用指针(Unsafe)的办法(转)

    近日由于在U3D项目中要使用到数据传递(C++ DLL的数据传递给U3D中的C#),其中涉及到需要使用C#的指针.直接编译会出现以下错误Unsafe code requires the 'unsafe ...

  5. C# 指针(unsafe与fixed的使用)

    c#在默认情况下生成的都是安全代码,即进行了代码托管(.NET的CLR机制好处之一是,进行代码托管,适时的释放内存,程序员便不必考虑资源的回收问题),而此时,指针不能出现在安全代码的编译条件下. 一. ...

  6. Golang指针

    学过C语言的老司机都知道,指针就是一个变量,用于存储另一个变量的内存地址. 那么什么是变量呢?在现代计算机体系结构中所有的需要执行的信息代码都需要存储在内存中,为了管理存储在内存的数据,内存是划分为不 ...

  7. Golang - 指针与引用

    ​ Golang有指针 , 那么一切数据都是值传递吗 ? 都需要用户进行指针传递吗, 其实不然, 对于Go语言, 虽然有指针, 但是其也有引用传递. 是不是很绕, 因为引用传递就是指针传递哇 . 我们 ...

  8. golang 指针类型学习

    对于指针类型不安全编程,通过指针值以及偏移量操作slice私有属性 // 通过指针值获取切片 func TestGetPrivate(t *testing.T) { s := []int{1, 2, ...

  9. Golang指针基本介绍及使用案例

    一.指针的相关概念说明 变量:是基本类型,变量存的就是值,也叫值类型 地址:用于引用计算机的内存地址,可理解为内存地址的标签,通俗一点讲就是一间房在小区里的门牌号.如下图① 指针:指针变量存的是一个地 ...

随机推荐

  1. 常用内置模块(三)--subprocess、re

    一.subprocess模块 进程:一个正在运行的程序 子进程:在父进程运行的过程中在其内部又开启了一个进程,即子进程. 作用:用于执行系统命令 os.system也可以获取当前的进程信息,但是它只能 ...

  2. 定时任务工具Linux crontab命令详解

    crontab:定时任务的守护进程,精确到分,设计秒的我们一般写脚本  -->相当于闹钟        日志文件:  ll /var/log/cron*        编辑文件: vim /et ...

  3. PAT 乙级 1005.继续(3n+1)猜想 C++/Java

    1005 继续(3n+1)猜想 (25 分) 题目来源  卡拉兹(Callatz)猜想已经在1001中给出了描述.在这个题目里,情况稍微有些复杂. 当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记 ...

  4. PAT 乙级 1076.Wifi密码

    题目来源 下面是微博上流传的一张照片:“各位亲爱的同学们,鉴于大家有时需要使用 wifi,又怕耽误亲们的学习,现将 wifi 密码设置为下列数学题答案:A-1:B-2:C-3:D-4:请同学们自己作答 ...

  5. Tensorcore使用方法

    用于深度学习的自动混合精度 深度神经网络训练传统上依赖IEEE单精度格式,但在混合精度的情况下,可以训练半精度,同时保持单精度网络的精度.这种同时使用单精度和半精度表示的技术称为混合精度技术. ​混合 ...

  6. Linux学习24-腾讯云服务器开启swap分区

    前言 最近有小伙伴买的腾讯云的1核1G入门级服务器,发现部署的服务多了后,会自动停掉一些docker的的容器. 新买的腾讯云主机没有提供Swap分区,理由是由于主机经常因为内存使用率过高,频繁使用Sw ...

  7. c# 调用 C++ dll 传入传出类型对应说明(转)

    由于经常使用C#调用 非托管C++ dll 操作一下硬件,出现传入传出类型的问题,现整理了C++ dll 类型与 C#类型对应关系: //C++中的DLL函数原型为        //extern & ...

  8. pip下载加速

    安装pqi pip install pqi pqi回车 pqi ls pqi tuna pqi show pip install --upgrade pqi git链接 https://github. ...

  9. easyui datebox 只显示日期,本文为转载,稍加改动

    var DateBoxHandler = {}; DateBoxHandler.onlyShowMonth = function(id) { function padding(v) {if (v &l ...

  10. python预课01 turtle学习

    Turtle命令: import turtle # 导入模块 t = turtle.Pen() # 生成画笔 t.speed() #设置速度0-10:0最快 t.forward() # 前进 t.ba ...