一、认识指针与指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:
ptr := &v // v 的类型为 T
其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,v的类型为 T,ptr 的类型为T,称做 T 的指针类型,代表指针。
func TestPtr(t *testing.T) {
s := "hello ptr"
fmt.Printf("s的地址为%p ,地址的10进制表示为%d 值为%s \n", &s, &s, s)
fmt.Printf("地址的2进制表示为%b", &s)
}
输出:
s的地址为0xc0000484e0 ,地址的10进制表示为824634016992 值为hello ptr
地址的2进制表示为1100000000000000000001001000010011100000
问题1:s地址占用了几个字节?
如果我们数一下大约有 40位二进制,那么是 5个字节?
正确答案是8个字节,我们可以转为unsafe.Pointer然后使用size方法打印出来
func TestPtr(t *testing.T) {
s := "hello ptr"
fmt.Printf("s的地址为%p ,地址的10进制表示为%d 值为%s \n", &s, &s, s)
fmt.Printf("s地址的2进制表示为%b \n", &s)
var p unsafe.Pointer
p = unsafe.Pointer(&s)
fmt.Printf("p地址的2进制表示为%b \n", p)
size := unsafe.Sizeof(p)
fmt.Printf("p地址的大小为几个字节 %d \n", size)
}
输出为:
s的地址为0xc00008e4d0 ,地址的10进制表示为824634303696 值为hello ptr
s地址的2进制表示为1100000000000000000010001110010011010000
p地址的2进制表示为1100000000000000000010001110010011010000
p地址的大小为几个字节 8
二、unsafe源码介绍
unsafe包常用方法
type ArbitraryType int
type Pointer *ArbitraryType
func Alignof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
- Alignof返回变量对齐字节数量
- Offsetof返回变量指定属性的偏移量,所以如果变量是一个struct类型,不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数。
- Sizeof 返回变量在内存中占用的字节数,切记,如果是slice,则不会返回这个slice在内存中的实际占用长度。
关于返回值 uintptr类型 在go源代码里
type uintptr uintptr
uintptr 是一个整数类型,它足够大,可以存储. 只有将Pointer转换成uintptr才能进行指针的相关操作。
uintptr是可以用于指针运算的,但是GC并不把uintptr当做指针,所以uintptr不能持有对象, 可能会被GC回收, 导致出现无法预知的错误. Pointer指向一个对象时, GC是不会回收这个内存的。
2.1 ArbitraryType 任意类型
ArbitraryType 表示任意类型,如同interface{},因为用来存储指针地址的,所以相当于存储任意类型。
type ArbitraryType int
2.2 Pointer 指针类型
unsafe中,ArbitraryType任意类型的的指针类型就是Pointer类型。
可以将其他类型都转换过来,然后通过这三个函数,分别能取长度,偏移量,对齐字节数,就可以在内存地址映射中,来回游走。
我们可以用强制类型转化type(a)语法把任意一个指针类型转成unsafe.Pointer
语法如下:
unsafe.Pointer(a)
func TestPointer(t *testing.T) {
//把一个int类型强制转成 unsafe.Pointer 任意type指针类型
var i int = 10
fmt.Println(unsafe.Pointer(&i)) //0xc0000a61a8
//把一个string类型强制转成 unsafe.Pointer 任意type指针类型
var s string = "hello"
fmt.Println(unsafe.Pointer(&s)) //0xc00008e4f0
//把一个array类型强制转成 unsafe.Pointer 任意type指针类型
var a [5]int = [5]int{0, 1, 2, 3, 4}
fmt.Println(unsafe.Pointer(&a)) //0xc0000b0030
//把一个map类型强制转成 unsafe.Pointer 任意type指针类型
var m map[string]int8 = map[string]int8{"a": 1, "b": 10, "c": 20, "d": 30}
fmt.Println(unsafe.Pointer(&m)) //0xc0000a0028
//把一个slice类型强制转成 unsafe.Pointer 任意type指针类型
var sli []int8 = []int8{0, 1, 3, 4, 5, 6}
fmt.Println(unsafe.Pointer(&sli)) //0xc0000b40a0
//把一个struct类型强制转成 unsafe.Pointer 任意type指针类型
type st struct {
w int8
h int8
}
var st1 = st{
w: 40,
h: 50,
}
fmt.Println(unsafe.Pointer(&st1)) //0xc0000a61b6
}
2.3 Sizeof 占用的内存大小
2.3.1、int等数字类型占用的内存大小
func TestSizeofInt(t *testing.T) {
//int8,int16,int32,int64,int类型占用的内存地址
var i8 int8 = 10
var i16 int16 = 10
var i32 int32 = 10
var i64 int64 = 10
var i int = 10
fmt.Println(unsafe.Sizeof(i8), unsafe.Sizeof(i16), unsafe.Sizeof(i32), unsafe.Sizeof(i64), unsafe.Sizeof(i))
//输出为 1 2 4 8 8
//uint8,uint16,uint32,uint64,uint类型占用的内存地址
var u8 uint8 = 10
var u16 uint16 = 10
var u32 uint32 = 10
var u64 uint64 = 10
var u uint = 10
fmt.Println(unsafe.Sizeof(u8), unsafe.Sizeof(u16), unsafe.Sizeof(u32), unsafe.Sizeof(u64), unsafe.Sizeof(u))
//输出为 1 2 4 8 8
}
类型type | 占用内存大小bit | 占用内存大小byte字节 |
---|---|---|
int8、uint8 | 8 | 1 |
int16、uint16 | 16 | 2 |
int32、uint32 | 32 | 4 |
int64、uint64 | 64 | 8 |
int、uint | 64 | 8 |
注意:int,uint是根据cpu来的,我这里是64位的cpu,所以这里占用了64bit内存,也有32位,16位的。
2.3.2、string类型占用的内存大小
func TestSizeofString(t *testing.T) {
//string类型占用的内存地址
var s string = "a"
fmt.Println(unsafe.Sizeof(s)) //16
//string类型编译后的存储类型为StringHeader,我们可以强制转化看一下
stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println(unsafe.Sizeof(stringHeader.Data)) //8
fmt.Println(unsafe.Sizeof(stringHeader.Len)) //8
byteStr := (*byte)(unsafe.Pointer(stringHeader.Data))
fmt.Println(byteStr) //0x11435f6 存储"a"的地址
fmt.Println(*byteStr) //97 十进制97
fmt.Println(string(*byteStr)) // "a" 字符串a
fmt.Printf("%b", *byteStr) //1100001 二进制标识的97
fmt.Println(unsafe.Sizeof(*byteStr)) //1 "a"存储占用的内存地址为1个字节
}
我们保存一个string类型的变量s,值为”a“
问题:此时使用unsafe.Sizeof(s)函数,得出内存占用为16个字节,为什么不是1?
答案:因为s在编译后的类型为 reflect.StringHeader 如下结构
type StringHeader struct {
Data uintptr
Len int
}
此时我们unsafe.Sizeof(s),其实是unsafe.Sizeof(StringHeader)
因为StringHeader.Data为uintptr类型占用8个字节,存储的字符串值的内存地址
StringHeader.Len为int类型也占用8个字节,所以总共16字节。
2.3.3 slice类型占用内存大小
func TestSizeofSlice(t *testing.T) {
//slice类型占用内存地址
var sli []int8 = []int8{0, 1, 2, 3, 4, 5}
fmt.Println(unsafe.Sizeof(sli)) //24
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&sli))
fmt.Println(unsafe.Sizeof(sliceHeader.Data)) //8 uintptr类型
fmt.Println(unsafe.Sizeof(sliceHeader.Len)) //8 int类型
fmt.Println(unsafe.Sizeof(sliceHeader.Cap)) //8 int类型
}
2.3.4、自定义struct占用用内大小
func TestSizeofStruct(t *testing.T) {
//struct类型占用的内存地址
type arrT struct {
v [100]int8
}
type ST struct {
b byte
i8 int8
sli []int8
s string
arrSt arrT
}
st := ST{
b: 1,
i8: 127,
sli: []int8{0, 1, 2, 3, 4},
s: "hello",
arrSt: arrT{v: [100]int8{5, 6, 7, 8}},
}
fmt.Println(unsafe.Sizeof(st)) //152 为结构体各个字段的内存之和
fmt.Println(unsafe.Sizeof(st.b)) //1 byte类型和uint8 一样占用1个字节
fmt.Println(unsafe.Sizeof(st.i8)) //1 int8类型占用1个字节
fmt.Println(unsafe.Sizeof(st.sli)) //24 slice类型占用24个字节
fmt.Println(unsafe.Sizeof(st.s)) //16 string类型16个字节
fmt.Println(unsafe.Sizeof(st.arrSt)) //100 [100]int8 数组类型为100*int8 为100个字节
}
一个结构体类型占用的内存为组成的各个字段的占用的内存之和。
2.4 Offsetof 指针的位移
Offsetof返回变量指定属性的偏移量,所以如果变量是一个struct类型,不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数。
func TestOffsetof(t *testing.T) {
var abc struct {
a bool
b int32
c []int
}
fmt.Println("SIZE")
fmt.Println(unsafe.Sizeof(abc.a)) //1
fmt.Println(unsafe.Sizeof(abc.b)) //4
fmt.Println(unsafe.Sizeof(abc.c)) //24
fmt.Println(unsafe.Sizeof(abc)) //32
fmt.Println("OFFSET")
fmt.Println(unsafe.Offsetof(abc.a)) //0
fmt.Println(unsafe.Offsetof(abc.b)) //4
fmt.Println(unsafe.Offsetof(abc.c)) //8
}
三、go的指针运算
go指针运算的语法:
pNew = unsafe.Pointer(uintptr(p) + offset)
p和pNew都是unsafe.Pointer类型
这里的p转成了uintptr然后和offset相加,是因为,以下两个用到的函数计算offset,返回值都是uintptr
func Offsetof(x ArbitraryType) uintptr
func Sizeof(x ArbitraryType) uintptr
最常见的用法是访问结构体中的字段或则数组的元素:
3.1 结构体的指针运算如下:
f := unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f))
// 等价于 f := unsafe.Pointer(&s.f)
指针的运算思想是:第一个的地址+偏移量
func TestPointer2(t *testing.T) {
var abc struct {
a bool
b int32
c []int
}
//1 结构体abc的地址为
fmt.Println(unsafe.Pointer(&abc))
//2 结构体abc.a 的地址为
fmt.Println(unsafe.Pointer(&abc.a))
//3 指针运算得出 abc.a的地址为
fmt.Println(unsafe.Pointer(uintptr(unsafe.Pointer(&abc)) + unsafe.Offsetof(abc.a)))
}
输出为:
=== RUN TestPointer2
0xc00008e060
0xc00008e060
0xc00008e060
0xc00008e064
0xc00008e064
--- PASS: TestPointer2 (0.00s)
- 我们可以得出结构体的地址为,第一个元素的地址
- 结构体的指针运算 没问题
3.2 数组的指针运算如下:
e := unsafe.Pointer(uintptr(unsafe.Pointer(&x[0])) + i*unsafe.Sizeof(x[0]))
// 等价于 e := unsafe.Pointer(&x[i])
指针的运算思想是:第一个的地址+偏移量
这里偏移量为 i*unsafe.Sizeof(x[0])) i为数组元素的索引index,sizeOf为数组元素的单个大小
func TestPointer3(t *testing.T) {
var i [4]uint32 = [4]uint32{}
//1 数组i的地址为
fmt.Println(unsafe.Pointer(&i))
//2 数组i[0],第一个元素的地址为
fmt.Println(unsafe.Pointer(&i[0]))
//3 数组i[1]的地址为
fmt.Println(unsafe.Pointer(&i[1]))
//4 通过指针运算 数组i[1] 的地址为
fmt.Println(unsafe.Pointer(uintptr(unsafe.Pointer(&i)) + 1*unsafe.Sizeof(i[0])))
//5 通过指针运算 最后一个 数组i[3] 的地址为
fmt.Println(unsafe.Pointer(uintptr(unsafe.Pointer(&i)) + 3*unsafe.Sizeof(i[0])))
}
3.3 取出内存外的数据会怎么样?
我们以数组为例子:取出index为10,公共才有4,超出了数组的范围
func TestPointer4(t *testing.T) {
var i [4]uint32 = [4]uint32{}
//5 通过指针运算 最后一个 数组i[3] 的地址为
fmt.Println(unsafe.Pointer(uintptr(unsafe.Pointer(&i)) + 3*unsafe.Sizeof(i[0])))
//6 超出的数组元素的范围会发生什么?--得到一个数组外其他内容的地址
fmt.Println(unsafe.Pointer(uintptr(unsafe.Pointer(&i)) + 10*unsafe.Sizeof(i[0])))
}
输出:
=== RUN TestPointer4
0xc00011e1cc
0xc00011e1e8
--- PASS: TestPointer4 (0.00s)
当我们试图打印超出的地址的内容时候,会报错!需要注意
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。