内存结构

字符串结构

1
2
3
4
5
6
type stringStruct struct {
    // 指向底层数组,连续分配的字节
    Data unsafe.Pointer
    // 记录着字符串的字节长度
    Len int 
}

字符串内存分布

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // 如上:str的地址为0x01f050存储的值是0x4a1160,0x4a1160则是字符h的首地址存储的值是0x68(字符h)
    var str string = "hello world!" // 把变量str当成StringStruct结构看待

    // 字符串的内存大小存在_type.size中,更多参考runtime/type.go文件
    sizeOf := unsafe.Sizeof(str)

    // 字符串占用内存大小(B):16
    fmt.Printf("字符串占用内存大小(B):%d\n", sizeOf)

    // &str:0x01f050
    // 这里需要明白的是结构体存储的是第一个字段的地址
    fmt.Printf("&str:%#x\n", &str) // &StringStruct.data

    // &StringStruct.Len
    l := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&str)) + unsafe.Sizeof(int(0))))

    // 字符串长度:12 (0x0c)
    fmt.Printf("字符串长度:%d\n", *l)

    data := *(*int)(unsafe.Pointer(&str))

    // 数据存储地址:0x4a1160
    fmt.Printf("数据存储地址:%#x\n", data)

    // 获取指定数据,不推荐分开操作指针比如这里的data写成两个表达式
    // uintptr(unsafe.Pointer(nil)) => 0
    //b0 := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) 
    //  + uintptr(data)))
    b0 := **(**byte)(unsafe.Pointer(&s1))	// h

    // str[0]:h
    fmt.Printf("str[0]:%c\n", b0)

    // 不建议这样分开写成两段,取str[8]的值
    //b8 := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + 
    //  uintptr(data) + 8*unsafe.Sizeof(byte('0'))))
    b8 := (**(**[9]byte)(unsafe.Pointer(&str)))[8]
    
    
    // str[8]:r
    fmt.Printf("str[8]:%c\n", b8)

    // Output:
    // 字符串占用内存大小(B):16
    // &str:0xc000088230
    // 字符串长度:12
    // 数据存储地址:0x103fb6b
    // str[0]:h
    // str[8]:r
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import (
    "fmt"
    "unsafe"
)

// stringHeader 字符串结构
type stringHeader struct {
    Data uintptr
    Len  uintptr
}

func main() {
    var s0 string = "hello"

    // 获取字符串长度
    l := (*stringHeader)(unsafe.Pointer(&s0)).Len

    fmt.Printf("字符串长度:%d\n", l)

    // s0[0]
    s00 := **(**byte)(unsafe.Pointer(&s0))

    fmt.Printf("s[0]:%c\n", s00)

    // s0[4]
    // *(*uintptr)(unsafe.Pointer(&s0))
    s04 := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + *(*uintptr)(unsafe.Pointer(&s0)) + 4*unsafe.Sizeof(byte('0'))))

    fmt.Printf("s[4]:%c\n", s04)
    
    // -------------------------------------------------------------------------------------------------------
    // 获取字符每个字符
    s1 := "helxo"

    fmt.Printf("%c\n", **(**byte)(unsafe.Pointer(&s1)))     // h
    fmt.Printf("%c\n", **(**uint16)(unsafe.Pointer(&s1)) >> 8)  // e
    fmt.Printf("%c\n", **(**uint32)(unsafe.Pointer(&s1)) >> 16 & 0b00000000_11111111)   // l
    fmt.Printf("%c\n", **(**uint32)(unsafe.Pointer(&s1)) >> 24) // x
    fmt.Printf("%c\n", **(**uint64)(unsafe.Pointer(&s1)) >> 32 & 0b00000000_00000000_00000000_11111111)	// o
    
    a := **(**[5]byte)(unsafe.Pointer(&s1))

    fmt.Println(a)
    
    // 字符不允许被修改,所以这里需要处理一下
    str1 := "hello world!" // 编译时会被分配到只读代码段
    str1 = str1 + " ds" // 运行时会分配到内存中,语言层面上限制了只读

    // ab1 是 [12]byte 类型
    ab1 := **(**[12]byte)(unsafe.Pointer(&str1))	// 对比下面的区别
    ab1[0] = 101
    fmt.Println(ab1, str1)  // [101 101 108 108 111 32 119 111 114 108 100 33] hello world! ds
    
    // ab 是 *[12]byte 类型
    ab := *(**[12]byte)(unsafe.Pointer(&str1))
    (*ab)[0] = 101
    fmt.Println(*ab, str1)  // [101 101 108 108 111 32 119 111 114 108 100 33] eello world! ds

    // 指针数组对比上面
    ac := new([12]byte) // *[12]byte
    (*ac)[0] = 96
    fmt.Println(*ac)    // [96 0 0 0 0 0 0 0 0 0 0 0]
}

字符串按值传参

  1. 以下我们可以看出,字符串在函数间传递传递的是stringStruct结构体。
  2. 按值传参也是直接新创建个变量地址保存stringStruct结构体,其字符串指向的底层数组是没有发生变化的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var str string = "hello world!"

    fmt.Printf("&str:%#x\n", &str)

    // 以下区别:
    // uintptr(unsafe.Pointer(&str))     得到uintptr类型就是&str地址,这是一个特例其他类型不允许这样转换
    // (*uintptr)(unsafe.Pointer(&str))  得到*uintptr类型也是&str地址,不建议使用
    // *(*uintptr)(unsafe.Pointer(&str)) 得到uintptr类型是str存储首uintptr长度字节的值
    l := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&str)) + unsafe.Sizeof(int(0))))

    // 字符串长度:12
    fmt.Printf("字符串长度:%d\n", *l)

    data := *(*int)(unsafe.Pointer(&str))

    // 数据存储地址:0x103fb6b
    fmt.Printf("数据存储地址:%#x\n", data)

    ts(str)

    // Output:
    // &str:0xc000036240
    // 字符串长度:12
    // 数据存储地址:0x9dfb6b
    // &s:0xc000036250
    // 字符串长度:12
    // 数据存储地址:0x9dfb6b
}

func ts(s string) {
    fmt.Printf("&s:%#x\n", &s)

    l := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) + unsafe.Sizeof(int(0))))

    // 字符串长度:12
    fmt.Printf("字符串长度:%d\n", *l)

    data := *(*int)(unsafe.Pointer(&s))

    // 数据存储地址:0x103fb6b
    fmt.Printf("数据存储地址:%#x\n", data)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func ts() {
    // 0xc00000a028 -> 0xc00001c0a8 -> 1
    str1 := 1    // int
    str := &str1 // *int

    fmt.Printf("%p\n", &str)                               // 0xc00000a028
    fmt.Printf("%p\n", str)                                // 0xc00001c0a8
    fmt.Printf("%#v\n", uintptr(unsafe.Pointer(&str)))     // 0xc00000a028
    fmt.Printf("%#v\n", (*uintptr)(unsafe.Pointer(&str)))  // (*uintptr)(0xc00000a028)
    fmt.Printf("%#v\n", *(*uintptr)(unsafe.Pointer(&str))) // 0xc00001c0a8
}

[]bytestring

  1. []bytestring都可以表示字符串,它们数据结构不同,其衍生出来的方法也不同。
  2. string擅长的场景:
    • 需要字符串比较、不需要nil字符串。
  3. []byte擅长的场景:
    • 修改字符串的时候、函数返回值,需要使用nil来表示含义、需要切片操作。

str2 := str1

  1. 字符串间赋值会共用同一个底层数组?
    • 字符串间赋值,【】共用同一个底层数组。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    var str1 = "hello"
    str2 := str1

    // 同用了一个底层数组
    // 因为字符串时只读类型,因此赋值共用同一个底层也没有问题。
    fmt.Println(*(*stringStruct)(unsafe.Pointer(&str1))) // {15567553 5}
    fmt.Println(*(*stringStruct)(unsafe.Pointer(&str2))) // {15567553 5}
}	

type stringStruct struct {
    str uintptr
    len uintptr
}

s := []byte(str)

  1. 字符串强制转成[]byte是否共用同一个底层数组?
    • 不会】同用一个底层数组。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    str := "hello"

    s := []byte(str)

    s[0] = 'e'

    fmt.Println(str, s)

    // 探究字符串强制转换成切片底层数组是否发生变化

    fmt.Printf("字符串指向底层数组:%x\n", *(*uintptr)(unsafe.Pointer(&str)))

    fmt.Printf("str[0]:%c\n", *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(nil)) + *(*uintptr)(unsafe.Pointer(&str)))))

    fmt.Printf("切片指向的底层数组:%x\n", *(*uintptr)(unsafe.Pointer(&s)))

    fmt.Printf("切片s[0]地址:%p\n", &s[0])

    // Output:
    // hello [101 101 108 108 111]
    // 字符串指向底层数组:12e9c2 
    // str[0]:h
    // 切片指向的底层数组:c00000e0b0
    // 切片s[0]地址:0xc00000e0b0
}

string[]byte

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
    s := "hello Go语言"

    // 下面代码可以看出,s和b并【没有】共用同一个底层数组
    b := []byte(s) // 这里会调用 runtime.stringtoslicebyte 函数

    fmt.Println(b)
}

stringtoslicebyte()

  1. stringslice bytestring -> []byte
  2. 参数:
    • buf *tmpBuftype tmpBuf [32]byte[32]byte数组用于转换大小在32字节的临时存储转换容器。上一章的"修改字符串"节string[]bytecap值为32就是这个参数的原因。
    • s string:转换字符串。
  3. 返回值:
    • []byte:转换后的slice byte。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func stringtoslicebyte(buf *tmpBuf, s string) []byte {
    var b []byte // nil
    // tmpBuf 容量够 s 的本次转换
    if buf != nil && len(s) <= len(buf) {
        // 这里也就是 []byte(string) 没用共用一个底层数组的原因所在
        *buf = tmpBuf{} // 清空 并 初始化新的容量 {0xxxx, 0, 32}
        b = buf[:len(s)]	
    } else { // 容器大小不够,需要重新申请大小
        b = rawbyteslice(len(s))
    }
    // copy([]byte, string) int
    copy(b, s) // 拷贝字符串中的数据
    return b
}

rawbyteslice()

  1. rawbyteslice分配一个新的byte slice。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// rawbyteslice allocates a new byte slice. The byte slice is not zeroed.
func rawbyteslice(size int) (b []byte) {
    // 因为是 byte 所以直接传 size 大小即可,匹配最近接的内存块规格大小
    cap := roundupsize(uintptr(size))   // 调整size大小
    // 这里是 []byte(string) 没用共用一个底层数组的原因所在
    p := mallocgc(cap, nil, false)      // 申请cap大小内存,p是申请后的内存地址
    if cap != uintptr(size) {
        // 清零多余的这部分内存块
        memclrNoHeapPointers(add(p, uintptr(size)), cap-uintptr(size))  
    }

    // 将申请的大小赋值个返回值b
    *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(cap)}
    return
}

[]bytestring

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
    b := []byte{'h', 'e', 'l', 'l', 'o'}

    // 下面源码可以看出,b和s并【没有】共用同一个底层数组
    s := string(b)  // 这里会调用 runtime.slicebytetostring 函数

    fmt.Println(s)
}

slicebytetostring()

  1. slicebytetostring将字节切片转换为字符串,string([]byte)
  2. 它由编译器插入到生成的代码中。
  3. ptr是一个指针,指向切片的第一个元素; n是切片的长度。
  4. buf是一个固定大小的缓冲区,如果结果没有escape转义字符,它就不是nil。
  5. 参数:
    • buf *tmpBuftype tmpBuf [32]byte[32]byte数组 用于转换大小在32字节的临时存储转换容器。
    • ptr *byte[]byte切片的底层数组地址,也就是slice.data的值。
    • n int:切片的长度,也就是slice.len的值。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// slicebytetostring converts a byte slice to a string.
// It is inserted by the compiler into generated code.
// ptr is a pointer to the first element of the slice;
// n is the length of the slice.
// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, ptr *byte, n int) (str string) {
    // 没有需要转换的
    if n == 0 {
        // Turns out to be a relatively common case.
        // Consider that you want to parse out data between parens in "foo()bar",
        // you find the indices and convert the subslice to string.
        return ""
    }
    if raceenabled {
        racereadrangepc(unsafe.Pointer(ptr),
            uintptr(n),
            getcallerpc(),
            abi.FuncPCABIInternal(slicebytetostring))
    }
    if msanenabled {
        msanread(unsafe.Pointer(ptr), uintptr(n))
    }
    if asanenabled {
        asanread(unsafe.Pointer(ptr), uintptr(n))
    }
    // 当[]byte 只有一个字符时
    if n == 1 {	
        // staticuint64s是一个[256]uint64的数组,也就是ASCII的数组数组
        // 这里可以看出并不是用的同一个底层数组。
        p := unsafe.Pointer(&staticuint64s[*ptr])	
        if goarch.BigEndian {	// 某些平台需要字节对齐
            p = add(p, 7)
        }
        stringStructOf(&str).str = p    // 赋值给字符串的str
        stringStructOf(&str).len = 1    // 赋值给字符串的len
        return
    }

    var p unsafe.Pointer
    if buf != nil && n <= len(buf) {    // 当长度在32范围内时
        p = unsafe.Pointer(buf)	// 直接使用buf的容量当做地址
    } else {
        p = mallocgc(uintptr(n), nil, false)    // 申请n大小的内存地址备用
    }
    stringStructOf(&str).str = p
    stringStructOf(&str).len = n
    memmove(p, unsafe.Pointer(ptr), uintptr(n)) // 将ptr地址长度为n字节的内容移动到p中
    return
}
1
2
3
4
5
6
7
8
type stringStruct struct {
    str unsafe.Pointer
    len int
}

func stringStructOf(sp *string) *stringStruct {
    return (*stringStruct)(unsafe.Pointer(sp))
}

string[]rune

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
    s := "hello Go语言"

    // 下面代码可以看出,s和b并【没有】共用同一个底层数组
    b := []rune(s)  // 这里会调用 runtime.stringtoslicerune 函数

    fmt.Println(b)
}

stringtoslicerune()

  1. stringslice rune[]rune(string)
  2. 参数:
    • buf *[tmpStringBufSize]rune*[32]rune 32位缓存rune。
    • s string:目标字符串。
  3. 返回值:
    • []rune:转换后的切片。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// string to slice rune
func stringtoslicerune(buf *[tmpStringBufSize]rune, s string) []rune {
    // two passes.
    // unlike slicerunetostring, no race because strings are immutable.
    n := 0
    for range s { // 遍历s统计rune的总量
        n++
    }

    var a []rune
    if buf != nil && n <= len(buf) { // 满足32字节
        *buf = [tmpStringBufSize]rune{}
        a = buf[:n]
    } else {
        a = rawruneslice(n) // 向系统申请n大小的内存
    }

    n = 0
    for _, r := range s { // 通过遍历将s存储到a中
        a[n] = r
        n++
    }
    return a
}

rawruneslice()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// rawruneslice allocates a new rune slice. The rune slice is not zeroed.
func rawruneslice(size int) (b []rune) {
    if uintptr(size) > maxAlloc/4 { // 内存溢出情景
        throw("out of memory")
    }
    // rune = int32 占4字节,匹配最接近的内存块
    mem := roundupsize(uintptr(size) * 4)
    // 这里是没有使用同一个地址的原因
    // 向系统申请内存的大小是直接,而size表示的字符的个数,所以这里是需要乘以4的
    p := mallocgc(mem, nil, false)	
    if mem != uintptr(size)*4 {
        memclrNoHeapPointers(add(p, uintptr(size)*4), mem-uintptr(size)*4) // 清零未使用的那部分内存块
    }

    *(*slice)(unsafe.Pointer(&b)) = slice{p, size, int(mem / 4)} // 申请后的赋值返回
    return
}

[]runestring

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

func main() {
    b := []rune{104, 101, 108, 108, 111, 32, 71, 111, 35821, 35328}

    // 下面源码可以看出,b和s并【没有】共用同一个底层数组
    s := string(b)  // 这里会调用 runtime.slicerunetostring 函数

    fmt.Println(s)
}

slicerunetostring()

  1. slice runestringstring([]rune)
  2. 参数:
    • buf *tmpBuftype tmpBuf [32]byte[32]byte数组用于转换大小在32字节的临时存储转换容器。
    • a []rune:转换的切片。
  3. 返回值:
    • string:转换后的字符串。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// slice rune to string
func slicerunetostring(buf *tmpBuf, a []rune) string {
    if raceenabled && len(a) > 0 {
        racereadrangepc(unsafe.Pointer(&a[0]),
            uintptr(len(a))*unsafe.Sizeof(a[0]),
            getcallerpc(),
            abi.FuncPCABIInternal(slicerunetostring))
    }
    if msanenabled && len(a) > 0 {
        msanread(unsafe.Pointer(&a[0]), uintptr(len(a))*unsafe.Sizeof(a[0]))
    }
    if asanenabled && len(a) > 0 {
        asanread(unsafe.Pointer(&a[0]), uintptr(len(a))*unsafe.Sizeof(a[0]))
    }
    var dum [4]byte // 临时容器
    // 记录[]rune切片转string需要的总字节数量 byte
    size1 := 0	 
    for _, r := range a {   // 【int rune】
        // encoderune 函数解码r并把值存入第一个参数中,返回解码的字节数量
        size1 += encoderune(dum[:], r)	
    }
    // 返回一个s和b底层数组相关联的,这里size1+3是为了兼容最后一个是ASCII情况
    s, b := rawstringtmp(buf, size1+3)	
    size2 := 0	// 统计数量
    for _, r := range a {
        // check for race
        // 可能存在 []rune 中 '' 这种数据
        if size2 >= size1 {
            break
        }
        // 也就是这里实现了将数据写入字符串地址中,因为切片和字符串共用同一个底层数组
        // encoderune该函数的作用是将r解码并存入第一个参数位置,并返回r的编码字节长度
        size2 += encoderune(b[size2:], r)	
    }
    return s[:size2]    // 切割字符串s得到的依然是字符串类型
}

rawstringtmp()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 返回一个s和b共用同一个底层数组
func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) {
    if buf != nil && l <= len(buf) {    // 如果满足32字节内
        b = buf[:l]
        s = slicebytetostringtmp(&b[0], len(b)) // 处理s和b的关联关系
    } else {
        s, b = rawstring(l) // 从新申请一块内存 关联s和b的底层数组关联关系
    }
    return
}

slicebytetostringtmp()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// slicebytetostringtmp returns a "string" referring to the actual []byte bytes.
//
// Callers need to ensure that the returned string will not be used after
// the calling goroutine modifies the original slice or synchronizes with
// another goroutine.
//
// The function is only called when instrumenting
// and otherwise intrinsified by the compiler.
//
// Some internal compiler optimizations use this function.
// - Used for m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)]
//   where k is []byte, T1 to Tn is a nesting of struct and array literals.
// - Used for "<"+string(b)+">" concatenation where b is []byte.
// - Used for string(b)=="foo" comparison where b is []byte.
func slicebytetostringtmp(ptr *byte, n int) (str string) {
    if raceenabled && n > 0 {
        racereadrangepc(unsafe.Pointer(ptr),
            uintptr(n),
            getcallerpc(),
            abi.FuncPCABIInternal(slicebytetostringtmp))
    }
    if msanenabled && n > 0 {
        msanread(unsafe.Pointer(ptr), uintptr(n))
    }
    if asanenabled && n > 0 {
        asanread(unsafe.Pointer(ptr), uintptr(n))
    }
    // 通过参数返回一个ptr和返回str相关联的底层数组
    stringStructOf(&str).str = unsafe.Pointer(ptr)	
    stringStructOf(&str).len = n
    return
}

rawstring()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// rawstring allocates storage for a new string. The returned
// string and byte slice both refer to the same storage.
// The storage is not zeroed. Callers should use
// b to set the string contents and then drop b.
func rawstring(size int) (s string, b []byte) {
    // 向系统申请内存
    p := mallocgc(uintptr(size), nil, false)

    stringStructOf(&s).str = p
    stringStructOf(&s).len = size

    *(*slice)(unsafe.Pointer(&b)) = slice{p, size, size}

    return
}

string -> string[:]

  1. 这种情况下没有新申请内存,而是【共用】的之前的内存。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package main

import "fmt"

func main() {
    s := "hello Go语言"

    // 字符串使用切片生成的依然是字符串类型,同用一个底层数据。
    // 下面我们来分析下main.main的汇编码 看看这一行是如何操作的
    s1 := s[:]

    fmt.Println(s1) // s1的类型为string
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# s1 := s[:] 情况
# 比较栈是否溢出 r14存储的当前groutine
string.go:5	0x496680    493b6610            cmp rsp, qword ptr [r14+0x10]	
string.go:5	0x496684    0f86b9000000        jbe 0x496743
# 给main.main函数栈预分配0x78大小
string.go:5	0x49668a    4883ec78            sub rsp, 0x78				  
# 将runtime.main函数的栈基址rsp入栈,以便main.main执行完好恢复
string.go:5	0x49668e    48896c2470          mov qword ptr [rsp+0x70], rbp	
# 将rbp指向main.main的新栈基位置,表示main.main的栈信息范围
string.go:5	0x496693    488d6c2470          lea rbp, ptr [rsp+0x70]			
# 该操作等于将字符串s的底层数组地址放入rcx
string.go:6	0x496698    488d0de87c0100      lea rcx, ptr [rip+0x17ce8]		
# 则部操作为给s.str赋值。s.data=rip+0x17ce8
string.go:6	0x49669f    48894c2438          mov qword ptr [rsp+0x38], rcx
# 该操作为给s.len赋值,标明字符串长度大小。s.len=14
string.go:6	0x4966a4    48c74424400e000000  mov qword ptr [rsp+0x40], 0xe		 
# 以下两行是第9行代码   s1 := s[:]	
# 可以看见是公共用的同一个底层数组。
string.go:9	0x4966ad    48894c2428          mov qword ptr [rsp+0x28], rcx		
string.go:9	0x4966b2    48c74424300e000000  mov qword ptr [rsp+0x30], 0xe
  • 修改第10行s1 := s[:]s1 := s[:5]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# s1 := s[:5] 情况
string.go:5	0x496680    493b6610            cmp rsp, qword ptr [r14+0x10]
string.go:5	0x496684    0f86c6000000        jbe 0x496750
string.go:5	0x49668a    4883ec78            sub rsp, 0x78
string.go:5	0x49668e    48896c2470          mov qword ptr [rsp+0x70], rbp
string.go:5	0x496693    488d6c2470          lea rbp, ptr [rsp+0x70]
# 这里几行给s赋值
string.go:6	0x496698    488d0de87c0100      lea rcx, ptr [rip+0x17ce8]		
string.go:6	0x49669f    48894c2438          mov qword ptr [rsp+0x38], rcx
string.go:6	0x4966a4    48c74424400e000000  mov qword ptr [rsp+0x40], 0xe
string.go:9	0x4966ad    eb00                jmp 0x4966af
string.go:9	0x4966af    eb00                jmp 0x4966b1
# 这几行s[:5],为什么这里的rip+0x17ccf和上面的rip+0x17ce8不一致?
# 原因是Go采用的地址加偏移量的形式,这里与前面的差量所以偏移量有所变化
# 还有个原因是编译阶段的字符串是被存储在代码段的,所以通过这种形式,
# 如果是被存储在栈或堆上呢,栈则是rsp+偏移量的形式
string.go:9	0x4966b1    488d0dcf7c0100      lea rcx, ptr [rip+0x17ccf]		
string.go:9	0x4966b8    48894c2428          mov qword ptr [rsp+0x28], rcx
string.go:9	0x4966bd    48c744243005000000  mov qword ptr [rsp+0x30], 0x5

# 更多s1 := s[2:5]基本和上面情况差不多

注意

  1. 不要命名标识符和包名称一样,这样会导致引用包名称时需要添加特殊别名称,比如命名函数名称bytes()和bytes包一致,导致bytes包需要别名称bytes2 "bytes"

参考

  1. 字符串内存布局
  2. 字符串内存布局
  3. 字符串内存
  4. unicode、utf8、utf16、utf32