[译]Go里面的unsafe包详解

unsafe包位置: src/unsafe/unsafe.go

https://gocn.vip/question/371

指针类型:
*类型:普通指针,用于传递对象地址,不能进行指针运算。
unsafe.Pointer:通用指针,用于转换不同类型的指针,不能进行指针运算。
uintptr:用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收。

unsafe.Pointer 可以和 普通指针 进行相互转换。
unsafe.Pointer 可以和 uintptr 进行相互转换。

也就是说 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。

一般使用流程:
第一步:将结构体->通用指针unsafe.Pointer(struct)->uintptr(通用指针)获取内存段的起始位置start_pos,并记录下来,第二步使用。
第二步:使用start_pos + unsafe.Offsetof(s.b)->将地址转为能用指针unsafe.Pointer(new_pos)->转为普通指针np = (*byte)(p)->赋值 *np = 20
第三步:打印结构体,发现内容发生了更改。

参考:https://www.cnblogs.com/golove/p/5909968.html

golang中slice切片理解总结

首先我们对切片有一个大概的理解,先看一下slice的内部结构,共分三部分,一个是指向底层数组的时候,一个是长度len,另一个就是slice的容量cap了。如cap不足以放在新值的时候,会产生新的内存地址申请。

先看代码

package main

import "fmt"

func main() {
	// 创建一个切片,长度为9,容量为10
	fmt.Println("----- 1.测试切片变量append的影响(未申请新的内存空间)-----")
	a := make([]int, 9, 10)
	fmt.Printf("%p len=%d cap=%d %v\n", a, len(a), cap(a), a)

	// 切片进行append操作,由于原来len(a)长度为9,而cap(a)容量为10,未达到扩展内存的要求,此时新创建的切片变量还指向原来的底层数组,只是数组的后面添加一个新值
	// 此时一共两个切片变量,一个是a,另一个是s4。但共指向的一个内存地址
	s4 := append(a, 4)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 测试上面提到的切片变量a和s4共指向同一个内存地址, 发现切片数组的第一个值都为7,而唯一不同的是len的长度,而cap仍为10
	fmt.Println("----- 2.测试切片变量共用一个底层数组(内存地址一样)-----")
	a[0] = 7
	fmt.Printf("%p len=%d cap=%d %v\n", a, len(a), cap(a), a)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 切片进行append操作后,发现原来的cap(a)的长度已用完了(因为a和s4共用一个底层数组,你也可以理解为cap(s4)),此时系统需要重新申请原cap*2大小的内存空间,所以cap值为10*2=20,将把原来底层数组的值复制到新的内存地址
	// 此时有两个底层数组,一个是切片变量a和s4指向的数组,另一个就是新的切片变量s4
	fmt.Println("----- 3.测试切片变量append的影响(申请了新的内存空间,内存地址不一样了)-----")
	s4 = append(s4, 5)
	fmt.Printf("%p len=%d cap=%d %v\n\n", s4, len(s4), cap(s4), s4)

	// 注意:原切片未发生任何变化,(打印a[0]=7是因为上面第3段落代码已把默认的0值改为了7)
	fmt.Println("----- 4.测试原切片变量a未发生变化-----")
	fmt.Printf("%p len=%d cap=%d %v\n\n", a, len(a), cap(a), a)
}

运行结果:

----- 1.测试切片变量append的影响(未申请新的内存空间)-----
0x10450030 len=9 cap=10 [0 0 0 0 0 0 0 0 0]
0x10450030 len=10 cap=10 [0 0 0 0 0 0 0 0 0 4]

----- 2.测试切片变量共用一个底层数组-----
0x10450030 len=9 cap=10 [7 0 0 0 0 0 0 0 0]
0x10450030 len=10 cap=10 [7 0 0 0 0 0 0 0 0 4]

----- 3.测试切片变量append的影响(申请了新的内存空间)-----
0x10458050 len=11 cap=20 [7 0 0 0 0 0 0 0 0 4 5]

----- 4.测试原切片变量a未发生变化-----
0x10450030 len=9 cap=10 [7 0 0 0 0 0 0 0 0]

在线运行代码:https://play.golang.org/p/pfxZa8T0H1_g

当对slice进行append的时候,会出现以下情况:
1.如果cap超出了原slice的cap长度,则申请原来cap*2的内存空间,再把原来切片所指的底层数组的值复制一份存储到新申请的内存空间里,这样是两块内存地址了,所有原来slice指的底层数组内容没有变化的。此时两个切片,两个底层数组了,每个切片有自己对应的底层数组。
2.如果cap没超出原slice的cap的话,底层对应的数组是没有变化的,但会产生一个新的slice变量,仍然会指向到原来的底层数组(这个新slice变量有自己的内存地址,我们不用关心它,只需要关心他对应的底层数组就可以了)。这时一共两个切片,但共用一个底层数组。

总结:
1.当对slice进行append的时候,如果原slice可以存放下新增加的值,则不会申请新的内存空间,否则会申请cap*2大小基准申请内存空间。而当cap>=1024的时候,会以cap*1.25倍的大小空间基准申请内存空间。
2.每次切片进行append操作,都会返回一个新的切片变量,这个变量有自己的内存地址。 这点开发者知道这点就可以了,一般不用不用关心这点。
3.当切片进行append后,原切片对应的底层数组是不会变化的,这点见第4点。

请参考golang开发源码包 src/runtime/slice.go 中的growslice函数,可以看到对于cap的增长处理逻辑。

Linux下对进程通信管理的信号机制概述

今天看到了篇使用golang实现的系统无感重启的文章,https://gravitational.com/blog/golang-ssh-bastion-graceful-restarts/,一般用来平滑处理一些系统服务,避免先停止再启用导致的服务不可用的情况。其中用到了信号机制,这里找了一些文章主要有来介绍这方面的文章,以便加深理解。https://blog.csdn.net/junyucsdn/article/details/50519248

https://blog.csdn.net/tiany524/article/details/17048069

https://my.oschina.net/chenliang165/blog/125825

Go中slice作为参数传递的一些“坑”

https://juejin.im/post/5a9f543a6fb9a028cb2d2b91

看明白了上面的文章,下面的例子基本也就明白了

 

package main

import "fmt"

func main() {
	a := []int{1,2,3}
	abc(a)
	fmt.Println(a)
}
func abc(a []int) {
	a[0] = 2 //修改后还是原来的a
	a = append(a, 4) // 此a非原a,使用append导致了重新分配内存地址(存储空间不足,系统自动分配一块新的足够大的内存地址,此时a的物理内存地址已经发行了变化,并将原来a的值copy一份到新的内存地址,所以这里修改的只是新内存地址的值,原来内存地址的值并没有改变),试着删除这行运行一次再看结果
	fmt.Println(a)
	
	a[0] = 7 // 新a,因为上面执行了append
	fmt.Println(a)
	
	fmt.Printf("\n===\n")
}

解释:

golang中string rune byte 三者的关系

在Golang中 string 底层是用byte字节数组存储的,并且是不可以修改的。

例如

s:="Go编程"
fmt.Println(len(s)) //输出结果应该是8因为中文字符是用3个字节存的(2+3*2=8)。
fmt.Printf("%d", len(string(rune('编')))) //经测试一个汉字确实占用3个字节,所以结果是3

如果想要获得字符个数的话,需要先转换为rune切片再使用内置的len函数

fmt.Println(len([]rune(s))) // 结果就是4了。

所以用string存储unicode的话,如果有中文,按下标是访问不到的,因为你只能得到一个byte。 要想访问中文的话,还是要用rune切片,这样就能按下表访问。

总结:
rune 能操作任何字符
byte 不支持中文的操作

示例:https://blog.haohtml.com/archives/14903