Go copy源码实现及内存重叠

2022/03/27

golang 中,copy 函数在字符串和切片中使用比较多,比如:切片和字符串类型相互强转、或者切片的数据拷贝时都会用到;那 copy 是怎么实现的来保证性能和安全呢?

在 runtime 的 go 文件中我们看不到 copy 的具体实现,只能借助于汇编分析来看它是怎么实现的

代码 demo

test.go

package main
import "fmt"
func main() {
   dst := make([]byte, 8)
   srcStr := "abc"
   srcByte := []byte{65, 66, 67}
   copy(dst, srcStr)
   copy(dst, srcByte)
   fmt.Println(dst, srcStr, srcByte)
}

我们调用 tool 工具 go tool compile -S -N -l test.go 来看它的源码实现

汇编代码

"".main STEXT size=1063 args=0x0 locals=0x160
        //-------------调度相关代码 头部 start -----------------------
        // 00000 ~ 00021主要作用: 检查函数栈帧是否够用,不够用跳到尾部进行扩容
        0x0000 00000 (jihui.go:5)       TEXT    "".main(SB), ABIInternal, $352-0 // 声明main函数, $352-0中:$240代表函数栈空间大小是352字节 ,0代表没有参数和返回值
        0x0000 00000 (jihui.go:5)       MOVQ    (TLS), CX // 把当前g的地址赋给CX寄存器
        0x0009 00009 (jihui.go:5)       LEAQ    -224(SP), AX // 把*(SP-224) 的地址赋值给AX寄存器
        0x0011 00017 (jihui.go:5)       CMPQ    AX, 16(CX) // 16(CX)对应g.stackguard0, 与AX寄存器进行比较
        0x0015 00021 (jihui.go:5)       JLS     1053 // 如果AX寄存器小于stackguard0,跳转到1053(0x041d 十六进制)这个位置,
         //-------------调度相关代码 头部 end -----------------------
        0x001b 00027 (jihui.go:5)       SUBQ    $352, SP // SP-352 使其指向栈顶位置,这行命令是为了分配栈帧空间, 让SP指向栈顶位置
        0x0022 00034 (jihui.go:5)       MOVQ    BP, 344(SP) // *(SP+344) = BP
        0x002a 00042 (jihui.go:5)       LEAQ    344(SP), BP // 把*(SP+344) 的地址赋值给BP寄存器, 使BP寄存器指向当前函数栈帧的栈底位置
        0x0032 00050 (jihui.go:5)       PCDATA  $0, $-2 // FUNCDATA 和 PCDATA均是gc使用,可忽略,后面把相关都删掉
        0x0032 00050 (jihui.go:5)       PCDATA  $1, $-2
        0x0032 00050 (jihui.go:5)       FUNCDATA        $0, gclocals·5ff117978f2c049ee09a6c769745bf8c(SB)
        0x0032 00050 (jihui.go:5)       FUNCDATA        $1, gclocals·552edf5372d5e9e9659db4bcf4b662b0(SB)
        0x0032 00050 (jihui.go:5)       FUNCDATA        $2, gclocals·cc71413ca7aa252461e8f7e24c7c2df4(SB)
        0x0032 00050 (jihui.go:5)       FUNCDATA        $3, "".main.stkobj(SB)
        //-------------对应第6行代码 start-------------//
        /**
        * slice底层的存储结构为
        *    type sliceHeader struct {
        *       Data unsafe.Pointer
        *       Len  int
        *       Cap  int
        *    }
        * 为dst变量分配内存与栈空间
        * 1. 通过runtime.makeslice为slice的Data申请内存空间,返回内存地址
        * 2. a - 把dst结构的Cap放到 sp + 192字节位置
        *    b - 把dst结构的Len放到 sp + 184字节位置
        *    c - 把dst结构的Data(makeslice返回地址)放到 sp + 176字节位置
        *    上面的栈空间对应dst变量
        */
        0x0032 00050 (jihui.go:6)       LEAQ    type.uint8(SB), AX //把type.uint8值的指针赋给AX
        0x0039 00057 (jihui.go:6)       MOVQ    AX, (SP) // 把寄存器里的值赋给sp
        0x003d 00061 (jihui.go:6)       MOVQ    $8, 8(SP) // 把len的值8赋给sp+8的位置
        0x0046 00070 (jihui.go:6)       MOVQ    $8, 16(SP) // 把cap的值8赋给sp+16的位置 (以上这几行都是为了给makeslice准备参数)
        ////从函数定义func makeslice(et *_type, len, cap int) unsafe.Pointer,可以看到 makeslice 需要三个参数:slice类型,slice长度、slice容量,返回值一个:slice创建后的地址指针
        0x004f 00079 (jihui.go:6)       CALL    runtime.makeslice(SB) // 调用makeslice
        0x0054 00084 (jihui.go:6)       MOVQ    24(SP), AX // AX = *(sp+24) 把makeslice的结果赋给AX
        0x0059 00089 (jihui.go:6)       MOVQ    AX, "".dst+176(SP) // AX 赋给变量dst 位置:sp + 176字节
        0x0061 00097 (jihui.go:6)       MOVQ    $8, "".dst+184(SP) // len:8 赋给变量dst 位置:sp + 184字节
        0x006d 00109 (jihui.go:6)       MOVQ    $8, "".dst+192(SP) // cap:8 赋给变量dst 位置:sp + 192字节
        //-------------对应第6行代码 end-------------//
        //-------------对应第7行代码 start-------------//
        /**
        * string底层的存储结构,比slice少了一个元素cap
        *    type stringHeader struct {
        *       str unsafe.Pointer
        *       len  int
        *    }
        * 为srcStr变量分配栈空间
        * 1. 和第6行不同的是,字面量常量abc已经分配在只读段rodata中,所以不需要再申请内存,只需要读取到该常量在rodata中的地址
        * 2. a - 把srcStr结构的len放到 sp + 128字节位置
        *    b - 把srcStr结构的str(rodata地址)放到 sp + 120字节位置
        *    上面的栈空间对应dst变量
        */
        0x0079 00121 (jihui.go:7)       LEAQ    go.string."abc"(SB), AX // AX 取abc这个.rodata段数据的地址;通常 string 常量是编译器分配到只读段的(.rodata),不在堆也不在栈,对应的数据地址不可修改
        0x0080 00128 (jihui.go:7)       MOVQ    AX, "".srcStr+120(SP) // 字符串abc的地址 赋给变量srcStr 位置:sp + 120字节
        0x0085 00133 (jihui.go:7)       MOVQ    $3, "".srcStr+128(SP) // 字符串abc的长度3 赋给变量srcStr 位置:sp + 128字节
        //-------------对应第7行代码 end-------------//
        //-------------对应第8行代码 start-------------//
        /**
        * slice底层的存储结构为
        *    type sliceHeader struct {
        *       Data unsafe.Pointer
        *       Len  int
        *       Cap  int
        *    }
        * 为srcByte变量分配内存与栈空间
        * 1. 通过runtime.newobject为slice的Data申请内存空间,返回内存地址并存入sp+80位置
        * 2. a - 第一个元素65存入 申请内存的第一个字节
        *    b - 第一个元素66存入 申请内存的第二个字节
        *    c - 第一个元素67存入 申请内存的第三个字节
        * 3. a - 把srcByte结构的Cap放到 sp + 168字节位置
        *    b - 把srcByte结构的Len放到 sp + 160字节位置
        *    c - 把srcByte结构的Data(newobject返回地址)放到 sp + 152字节位置
        *    上面的栈空间对应srcByte变量
        */
        0x0091 00145 (jihui.go:8)       LEAQ    type.[3]uint8(SB), AX //把type.[3]uint8值的指针赋给AX
        0x0098 00152 (jihui.go:8)       MOVQ    AX, (SP) // 把寄存器里的值赋给sp (为了给newobject准备参数)
        0x009c 00156 (jihui.go:8)       CALL    runtime.newobject(SB) //调用函数 func newobject(typ *_type) unsafe.Pointer 分配内存
        0x00a1 00161 (jihui.go:8)       MOVQ    8(SP), AX // AX = *(sp+8) 把runtime.newobject的结果赋给AX
        0x00a6 00166 (jihui.go:8)       MOVQ    AX, ""..autotmp_4+80(SP) //把runtime.newobject的结果赋给变量autotmp_4 位置:sp + 80字节
        0x00ab 00171 (jihui.go:8)       MOVB    $65, (AX) //byte:65 放入autotmp_4第一个字节
        0x00ae 00174 (jihui.go:8)       MOVQ    ""..autotmp_4+80(SP), AX
        0x00b3 00179 (jihui.go:8)       TESTB   AL, (AX)
        0x00b5 00181 (jihui.go:8)       MOVB    $66, 1(AX) //byte:66 放入autotmp_4第二个字节
        0x00b9 00185 (jihui.go:8)       MOVQ    ""..autotmp_4+80(SP), AX
        0x00be 00190 (jihui.go:8)       TESTB   AL, (AX)
        0x00c0 00192 (jihui.go:8)       MOVB    $67, 2(AX) //byte:67 放入autotmp_4第三个字节
        0x00c4 00196 (jihui.go:8)       MOVQ    ""..autotmp_4+80(SP), AX
        0x00c9 00201 (jihui.go:8)       TESTB   AL, (AX)
        0x00cb 00203 (jihui.go:8)       JMP     205 //0x00cd
        0x00cd 00205 (jihui.go:8)       MOVQ    AX, "".srcByte+152(SP) // runtime.newobject 赋给变量srcByte 位置:sp + 152字节;srcByte的数据分配在栈中
        0x00d5 00213 (jihui.go:8)       MOVQ    $3, "".srcByte+160(SP) // len:3 赋给变量srcByte 位置:sp + 160字节
        0x00e1 00225 (jihui.go:8)       MOVQ    $3, "".srcByte+168(SP) // cap:3 赋给变量srcByte 位置:sp + 168字节
        //-------------对应第8行代码 end-------------//
        //-------------对应第9行代码 start-------------//
        /**
        * 把string拷贝到[]byte中
        * 1. 和第6行不同的是,字面量常量abc已经分配在只读段rodata中,所以不需要再申请内存,只需要读取到该常量在rodata中的地址
        * 2. a - 把srcStr结构的len放到 sp + 128字节位置
        *    b - 把srcStr结构的str(rodata地址)放到 sp + 120字节位置
        *    上面的栈空间对应dst变量
        */
        0x00ed 00237 (jihui.go:9)       MOVQ    "".dst+184(SP), AX //变量dst的长度 len:8 放入AX寄存器
        0x00f5 00245 (jihui.go:9)       MOVQ    "".dst+192(SP), CX //变量dst的容量 容量:8 放入CX寄存器
        0x00fd 00253 (jihui.go:9)       MOVQ    "".dst+176(SP), DX //变量dst的值指针 放入DX寄存器
        0x0105 00261 (jihui.go:9)       MOVQ    DX, ""..autotmp_5+224(SP) //变量dst的值指针 赋值给变量 autotmp_5 位置:sp + 224字节
        0x010d 00269 (jihui.go:9)       MOVQ    AX, ""..autotmp_5+232(SP) //变量dst的长度len 赋值给变量 autotmp_5 位置:sp + 232字节
        0x0115 00277 (jihui.go:9)       MOVQ    CX, ""..autotmp_5+240(SP) //变量dst的容量cap 赋值给变量 autotmp_5 位置:sp + 240字节
        0x011d 00285 (jihui.go:9)       MOVQ    "".srcStr+120(SP), AX //变量srcStr的值指针 放入AX寄存器
        0x0122 00290 (jihui.go:9)       MOVQ    "".srcStr+128(SP), CX //变量srcStr的长度 len:3 放入CX寄存器
        0x012a 00298 (jihui.go:9)       MOVQ    AX, ""..autotmp_6+136(SP) //变量srcStr的值指针 赋值给变量autotmp_6 位置:sp + 136字节
        0x0132 00306 (jihui.go:9)       MOVQ    CX, ""..autotmp_6+144(SP) //变量srcStr的长度 赋值给变量autotmp_6 位置:sp + 144字节
        0x013a 00314 (jihui.go:9)       MOVQ    ""..autotmp_5+232(SP), AX //autotmp_5的长度(也即是dst的长度),赋值给AX
        0x0142 00322 (jihui.go:9)       MOVQ    AX, ""..autotmp_7+56(SP) //autotmp_5的长度(也即是dst的长度),赋值给变量autotmp_7 位置:sp + 56字节
        0x0147 00327 (jihui.go:9)       CMPQ    ""..autotmp_6+144(SP), AX //比较autotmp_6和autotmp_5的长度,也即是dst和srcStr的长度
        0x014f 00335 (jihui.go:9)       JLT     342 //0x0156 srcStr.len < dst.len
        0x0151 00337 (jihui.go:9)       JMP     1048 //0x0418
        0x0156 00342 (jihui.go:9)       MOVQ    ""..autotmp_6+144(SP), AX //把autotmp_6的len字段存入AX
        0x015e 00350 (jihui.go:9)       MOVQ    AX, ""..autotmp_7+56(SP) //前面几行的逻辑是比较dst和srcStr的长度len,把最小值赋值给变量autotmp_7 位置:sp + 56
        0x0163 00355 (jihui.go:9)       JMP     357 //0x0165
        0x0165 00357 (jihui.go:9)       MOVQ    ""..autotmp_6+136(SP), AX //srcStr的数据地址赋值给AX
        0x016d 00365 (jihui.go:9)       CMPQ    ""..autotmp_5+224(SP), AX //比较dst的数据地址和srcStr的数据地址
        0x0175 00373 (jihui.go:9)       JNE     380 //0x017c 地址不相等
        0x0177 00375 (jihui.go:9)       JMP     1043 //0x0413 如果相等,说明两个数据指向同一个地址,不需要拷贝,跳转到0x0413,然后再跳转到0x01b0;这种情况下上面的两个len比较逻辑可以去掉,因为取最小len主要是为了下面的memmove调用提供参数,这里就用不到了
        0x017c 00380 (jihui.go:9)       MOVQ    ""..autotmp_7+56(SP), AX
        0x0181 00385 (jihui.go:9)       MOVQ    AX, ""..autotmp_8+48(SP)
        0x0186 00390 (jihui.go:9)       MOVQ    ""..autotmp_5+224(SP), AX
        0x018e 00398 (jihui.go:9)       MOVQ    AX, (SP)
        0x0192 00402 (jihui.go:9)       MOVQ    ""..autotmp_6+136(SP), AX
        0x019a 00410 (jihui.go:9)       MOVQ    AX, 8(SP)
        0x019f 00415 (jihui.go:9)       MOVQ    ""..autotmp_8+48(SP), AX
        0x01a4 00420 (jihui.go:9)       MOVQ    AX, 16(SP) //如果不相等,则为runtime.memmove函数准备三个参数:目的dst的数据地址、源srcStr的数据地址、源和目的的最小len
        0x01a9 00425 (jihui.go:9)       CALL    runtime.memmove(SB) //调用memmove(to, from unsafe.Pointer, n uintptr)
        0x01ae 00430 (jihui.go:9)       JMP     432 //0x01b0
        //-------------对应第9行代码 end-------------//
        //-------------对应第10行代码 start-------------//
        //处理逻辑和第9行基本一样,虽然第9行拷贝的是string,这里拷贝的是[]byte类型;因为string和[]byte的底层结构类似,第一个元素都指向实际数据的地址
        0x01b0 00432 (jihui.go:10)      MOVQ    "".dst+176(SP), AX
        0x01b8 00440 (jihui.go:10)      MOVQ    "".dst+184(SP), CX
        0x01c0 00448 (jihui.go:10)      MOVQ    "".dst+192(SP), DX
        0x01c8 00456 (jihui.go:10)      MOVQ    AX, ""..autotmp_9+200(SP)
        0x01d0 00464 (jihui.go:10)      MOVQ    CX, ""..autotmp_9+208(SP)
        0x01d8 00472 (jihui.go:10)      MOVQ    DX, ""..autotmp_9+216(SP)
        0x01e0 00480 (jihui.go:10)      MOVQ    "".srcByte+152(SP), AX
        0x01e8 00488 (jihui.go:10)      MOVQ    "".srcByte+160(SP), CX
        0x01f0 00496 (jihui.go:10)      MOVQ    "".srcByte+168(SP), DX
        0x01f8 00504 (jihui.go:10)      MOVQ    AX, ""..autotmp_10+272(SP)
        0x0200 00512 (jihui.go:10)      MOVQ    CX, ""..autotmp_10+280(SP)
        0x0208 00520 (jihui.go:10)      MOVQ    DX, ""..autotmp_10+288(SP)
        0x0210 00528 (jihui.go:10)      MOVQ    ""..autotmp_9+208(SP), AX
        0x0218 00536 (jihui.go:10)      MOVQ    AX, ""..autotmp_11+72(SP)
        0x021d 00541 (jihui.go:10)      CMPQ    ""..autotmp_10+280(SP), AX
        0x0225 00549 (jihui.go:10)      JLT     556
        0x0227 00551 (jihui.go:10)      JMP     1038
        0x022c 00556 (jihui.go:10)      MOVQ    ""..autotmp_10+280(SP), AX
        0x0234 00564 (jihui.go:10)      MOVQ    AX, ""..autotmp_11+72(SP)
        0x0239 00569 (jihui.go:10)      JMP     571
        0x023b 00571 (jihui.go:10)      MOVQ    ""..autotmp_10+272(SP), AX
        0x0243 00579 (jihui.go:10)      CMPQ    ""..autotmp_9+200(SP), AX
        0x024b 00587 (jihui.go:10)      JNE     594
        0x024d 00589 (jihui.go:10)      JMP     1033
        0x0252 00594 (jihui.go:10)      MOVQ    ""..autotmp_11+72(SP), AX
        0x0257 00599 (jihui.go:10)      MOVQ    AX, ""..autotmp_12+64(SP)
        0x025c 00604 (jihui.go:10)      MOVQ    ""..autotmp_9+200(SP), AX
        0x0264 00612 (jihui.go:10)      PCDATA  $0, $0
        0x0264 00612 (jihui.go:10)      MOVQ    AX, (SP)
        0x0268 00616 (jihui.go:10)      MOVQ    ""..autotmp_10+272(SP), AX
        0x0270 00624 (jihui.go:10)      MOVQ    AX, 8(SP)
        0x0275 00629 (jihui.go:10)      MOVQ    ""..autotmp_12+64(SP), AX
        0x027a 00634 (jihui.go:10)      MOVQ    AX, 16(SP)
        0x027f 00639 (jihui.go:10)      CALL    runtime.memmove(SB)
        0x0284 00644 (jihui.go:10)      JMP     646
        //-------------对应第10行代码 end-------------//
        //-------------对应第11行代码 start-------------//
        0x0286 00646 (jihui.go:11)      MOVQ    "".dst+176(SP), AX
        0x028e 00654 (jihui.go:11)      MOVQ    "".dst+184(SP), CX
        0x0296 00662 (jihui.go:11)      MOVQ    "".dst+192(SP), DX
        0x029e 00670 (jihui.go:11)      MOVQ    AX, (SP)
        0x02a2 00674 (jihui.go:11)      MOVQ    CX, 8(SP)
        0x02a7 00679 (jihui.go:11)      MOVQ    DX, 16(SP)
        0x02ac 00684 (jihui.go:11)      CALL    runtime.convTslice(SB)
        0x02b1 00689 (jihui.go:11)      MOVQ    24(SP), AX
        0x02b6 00694 (jihui.go:11)      MOVQ    AX, ""..autotmp_13+112(SP)
        0x02bb 00699 (jihui.go:11)      MOVQ    "".srcStr+120(SP), AX
        0x02c0 00704 (jihui.go:11)      MOVQ    "".srcStr+128(SP), CX
        0x02c8 00712 (jihui.go:11)      MOVQ    AX, (SP)
        0x02cc 00716 (jihui.go:11)      MOVQ    CX, 8(SP)
        0x02d1 00721 (jihui.go:11)      CALL    runtime.convTstring(SB)
        0x02d6 00726 (jihui.go:11)      MOVQ    16(SP), AX
        0x02db 00731 (jihui.go:11)      MOVQ    AX, ""..autotmp_14+104(SP)
        0x02e0 00736 (jihui.go:11)      MOVQ    "".srcByte+152(SP), AX
        0x02e8 00744 (jihui.go:11)      MOVQ    "".srcByte+160(SP), CX
        0x02f0 00752 (jihui.go:11)      MOVQ    "".srcByte+168(SP), DX
        0x02f8 00760 (jihui.go:11)      MOVQ    AX, (SP)
        0x02fc 00764 (jihui.go:11)      MOVQ    CX, 8(SP)
        0x0301 00769 (jihui.go:11)      MOVQ    DX, 16(SP)
        0x0306 00774 (jihui.go:11)      CALL    runtime.convTslice(SB)
        0x030b 00779 (jihui.go:11)      MOVQ    24(SP), AX
        0x0310 00784 (jihui.go:11)      MOVQ    AX, ""..autotmp_15+96(SP)
        0x0315 00789 (jihui.go:11)      XORPS   X0, X0
        0x0318 00792 (jihui.go:11)      MOVUPS  X0, ""..autotmp_3+296(SP)
        0x0320 00800 (jihui.go:11)      XORPS   X0, X0
        0x0323 00803 (jihui.go:11)      MOVUPS  X0, ""..autotmp_3+312(SP)
        0x032b 00811 (jihui.go:11)      XORPS   X0, X0
        0x032e 00814 (jihui.go:11)      MOVUPS  X0, ""..autotmp_3+328(SP)
        0x0336 00822 (jihui.go:11)      LEAQ    ""..autotmp_3+296(SP), AX
        0x033e 00830 (jihui.go:11)      MOVQ    AX, ""..autotmp_17+88(SP)
        0x0343 00835 (jihui.go:11)      TESTB   AL, (AX)
        0x0345 00837 (jihui.go:11)      MOVQ    ""..autotmp_13+112(SP), CX
        0x034a 00842 (jihui.go:11)      LEAQ    type.[]uint8(SB), DX
        0x0351 00849 (jihui.go:11)      MOVQ    DX, ""..autotmp_3+296(SP)
        0x0359 00857 (jihui.go:11)      MOVQ    CX, ""..autotmp_3+304(SP)
        0x0361 00865 (jihui.go:11)      TESTB   AL, (AX)
        0x0363 00867 (jihui.go:11)      MOVQ    ""..autotmp_14+104(SP), AX
        0x0368 00872 (jihui.go:11)      LEAQ    type.string(SB), CX
        0x036f 00879 (jihui.go:11)      MOVQ    CX, ""..autotmp_3+312(SP)
        0x0377 00887 (jihui.go:11)      MOVQ    AX, ""..autotmp_3+320(SP)
        0x037f 00895 (jihui.go:11)      MOVQ    ""..autotmp_17+88(SP), AX
        0x0384 00900 (jihui.go:11)      TESTB   AL, (AX)
        0x0386 00902 (jihui.go:11)      MOVQ    ""..autotmp_15+96(SP), CX
        0x038b 00907 (jihui.go:11)      LEAQ    type.[]uint8(SB), DX
        0x0392 00914 (jihui.go:11)      MOVQ    DX, 32(AX)
        0x0396 00918 (jihui.go:11)      LEAQ    40(AX), DI
        0x039a 00922 (jihui.go:11)      CMPL    runtime.writeBarrier(SB), $0
        0x03a1 00929 (jihui.go:11)      JEQ     933
        0x03a3 00931 (jihui.go:11)      JMP     1023
        0x03a5 00933 (jihui.go:11)      MOVQ    CX, 40(AX)
        0x03a9 00937 (jihui.go:11)      JMP     939
        0x03ab 00939 (jihui.go:11)      MOVQ    ""..autotmp_17+88(SP), AX
        0x03b0 00944 (jihui.go:11)      TESTB   AL, (AX)
        0x03b2 00946 (jihui.go:11)      JMP     948
        0x03b4 00948 (jihui.go:11)      MOVQ    AX, ""..autotmp_16+248(SP)
        0x03bc 00956 (jihui.go:11)      MOVQ    $3, ""..autotmp_16+256(SP)
        0x03c8 00968 (jihui.go:11)      MOVQ    $3, ""..autotmp_16+264(SP)
        0x03d4 00980 (jihui.go:11)      MOVQ    AX, (SP)
        0x03d8 00984 (jihui.go:11)      MOVQ    $3, 8(SP)
        0x03e1 00993 (jihui.go:11)      MOVQ    $3, 16(SP)
        0x03ea 01002 (jihui.go:11)      CALL    fmt.Println(SB)
        //-------------对应第11行代码 end-------------//
        0x03ef 01007 (jihui.go:12)      MOVQ    344(SP), BP
        0x03f7 01015 (jihui.go:12)      ADDQ    $352, SP
        0x03fe 01022 (jihui.go:12)      RET
        0x03ff 01023 (jihui.go:11)      MOVQ    CX, AX
        0x0402 01026 (jihui.go:11)      CALL    runtime.gcWriteBarrier(SB)
        0x0407 01031 (jihui.go:11)      JMP     939
        0x0409 01033 (jihui.go:10)      JMP     646
        0x040e 01038 (jihui.go:10)      JMP     571
        0x0413 01043 (jihui.go:9)       JMP     432 //0x01b0
        0x0418 01048 (jihui.go:9)       JMP     357 //0x0165
        0x041d 01053 (jihui.go:9)       NOP
        //-------------调度相关代码 尾部 start --------------
        // 01053 主要作用:1.栈扩容;2.被runtime管理调度
        0x041d 01053 (jihui.go:5)       CALL    runtime.morestack_noctxt(SB) //// morestack but not preserving ctxt. 执行栈空间扩容
        0x0422 01058 (jihui.go:5)       PCDATA  $0, $-1
        0x0422 01058 (jihui.go:5)       JMP     0
        //------------调度相关代码 尾部 end -----------------------


注:_type 字段的内容都会被编译成常量放入了 data 区,同一个类型的类型信息将被放在 data 区同一个地方,所以可以通过比较指针值来判断能否断言

从上面汇编实现可以看到,copy 的实现逻辑大概包括:

  1. 比较 dst 和 src 的 len,最小值存入 len
  2. 构造参数 dst intptr、src intptr、new len
  3. 调用 runtime.memmove 实现拷贝来解决内存重叠问题

内存重叠

那什么是内存重叠呢?

1 2 3 4 5 6
srcStart . . . srcEnd .
a b c d e .
. dstStart . . . dstEnd
. a a a a a

如上面所示,源字符串 src 为 a、b、c、d、e,其内存地址为 1、2、3、4、5;我们想把源字符串移动到新的字符串 dst,其对应内存地址为 2、3、4、5、6。如果从左到右移动的话,就会有问题

我们看看怎么实现的,首先 src 的最左边 1 移动到 dst 的最左边也即是位置 2,2 的数据就变成了 a;当移动 src 第二个位置时,因为刚才的操作把位置 2 的 b 覆盖成了 a,所以最终 dst 的数据变成了 a、a、a、a、a 不是我们预期的效果

这种就是因为 dst 和 src 的内存地址重叠,导致已处理的数据覆盖未处理的数据导致

那怎么避免这种情况呢?

是不是从右往左拷贝就可以了,答案是的

所以 memmove 会先判断是否发生内存重叠。如果重叠的话,是左边重叠还是右边重叠来决定往哪边挪,像上面情况会从右往左挪;而如果 dst 的内存地址是 0、1、2、3、4 时,则从左往右拷贝

Post Directory