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 的实现逻辑大概包括:
- 比较 dst 和 src 的 len,最小值存入 len
- 构造参数 dst intptr、src intptr、new len
- 调用 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 时,则从左往右拷贝