**获取 slice 长度 **
通过对slice的学习,我们知道了 slice header 的结构体定义:
```go
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
```
调用 make 函数新建一个 slice,底层调用的是 makeslice 函数,返回的是 slice 结构体:
``` func makeslice(et *_type, len, cap int) slice```
因此我们可以通过 unsafe.Pointer 和 uintptr 进行转换,得到 slice 的字段值。
```go
func main() {
s := make([]int, 9, 20)
var Len = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(8)))
fmt.Println(Len, len(s)) // 9 9
var Cap = *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + uintptr(16)))
fmt.Println(Cap, cap(s)) // 20 20
}
```
上文采用加uintptr(8)和uintptr(16),是因为底层slice结构连续的,切片变量 s 在内存中占用了连续的 3 个机器字。在 64 位架构下,一个机器字的大小为 8 个字节,因此 s 的第一个机器字是指向底层数组的指针,第二个机器字是切片的长度,第三个机器字是切片的容量。可能有些小伙伴不了解字的含义,这里和大家解释一下:
```
16位系统: 1个机器字---2个字节--16位bit
32位系统: 1个机器字---4个字节--32位bit
64位系统: 1个及其子---8个字节--64位bit
其中:字节是内存操作的基本单位,所以uintptr(8)才能计算偏移位置。
```
slice内存结构如下:
```go
+--------------+
| Ptr | -- 指向底层数组的指针
+--------------+
| Len | -- 切片的长度
+--------------+
| Cap | -- 切片的容量
+--------------+
```
Len,cap 的转换流程如下:
Len: &s => pointer => uintptr => pointer => *int => int
Cap: &s => pointer => uintptr => pointer => *int => int
**获取 map 长度 **
再来看一下 map:
```go
type hmap struct {
count int
flags uint8
B uint8
noverflow uint16
hash0 uint32
buckets unsafe.Pointer
oldbuckets unsafe.Pointer
nevacuate uintptr
extra *mapextra
}
```
和 slice 不同的是,makemap 函数返回的是 hmap 的指针,注意是指针:
``` func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap```
我们依然能通过 unsafe.Pointer 和 uintptr 进行转换,得到 hmap 字段的值,只不过,现在 count 变成二级指针了:
```go
func main() {
mp := make(map[string]int)
mp["qcrao"] = 100
mp["stefno"] = 18
count := **(**int)(unsafe.Pointer(&mp))
fmt.Println(count, len(mp)) // 2 2
}
```
count 的转换过程:
&mp => pointer => **int => int
注意:上面的转化,是因为hmap第一个成员是count,所以可以将*hmap指针转化为*int指针。转化后获取的就是count地址,然后进行(**int)取值就可以获取数量了。
有疑问加站长微信联系(非本文作者)