2在大多数情况下,memmove()函数调用的成本与memcpy()在任何定义了它们行为的场景中并没有显著差异。然而,还有两点尚未提及:
1. 在某些实现中,地址重叠的确定可能是昂贵的。在标准C中没有办法确定源对象和目标对象是否指向同一块分配的内存区域,因此不能使用大于号或小于号运算符来比较它们,否则会导致未定义行为。任何实际的实现可能都有一些有效的方法来确定指针是否重叠,但标准并不要求这样的方法存在。完全使用可移植的C编写的memmove()函数在许多平台上执行的时间可能至少是完全使用可移植的C编写的memcpy()函数的两倍。
2. 当不改变语义时,实现允许将函数展开。在80x86编译器上,如果ESI和EDI寄存器没有保持任何重要内容,memcpy(src, dest, 1234)可以生成以下代码:
```
mov esi,[src]
mov edi,[dest]
mov ecx,1234/4 ; 编译器可以注意到它是一个常量
cld
rep movsl
```
这将花费相同数量的内联代码,但比以下代码运行得更快:
```
push [src]
push [dest]
push dword 1234
call _memcpy
...
_memcpy:
push ebp
mov ebp,esp
mov ecx,[ebp+numbytes]
test ecx,3 ; 看看它是否是四的倍数
jz multiple_of_four
multiple_of_four:
push esi ; 不知道调用方是否需要保留此值
push edi ; 不知道调用方是否需要保留此值
mov esi,[ebp+src]
mov edi,[ebp+dest]
rep movsl
pop edi
pop esi
ret
```
相当多的编译器都会对memcpy()进行这样的优化。我不知道有哪些编译器会对memmove()进行这样的优化,尽管在某些情况下,一个优化版本的memcpy()可能提供与memmove相同的语义。例如,如果numbytes为20:
```
; 假设eax、ebx、ecx、edx、esi和edi中的值不需要
mov esi,[src]
mov eax,[esi]
mov ebx,[esi+4]
mov ecx,[esi+8]
mov edx,[esi+12]
mov edi,[esi+16]
mov esi,[dest]
mov [esi],eax
mov [esi+4],ebx
mov [esi+8],ecx
mov [esi+12],edx
mov [esi+16],edi
```
即使地址范围重叠,这也将正常工作,因为它在写入任何部分之前有效地复制(在寄存器中)要移动的整个区域。理论上,编译器可以通过查看是否将memmove()视为memcpy()会产生一个即使地址范围重叠也是安全的实现,并在这些情况下调用_memmove。我不知道有哪些编译器进行了这样的优化。
- supercat