了解memcpy()的实现

我正在寻找memcpy.c的实现,我发现了一个不同的memcpy代码。 我不明白为什么他们这样做(((地址)s)|((ADDRESS)d)| c)&(sizeof(UINT) – 1)

#if !defined(__MACHDEP_MEMFUNC) #ifdef _MSC_VER #pragma function(memcpy) #undef __MEMFUNC_ARE_INLINED #endif #if !defined(__MEMFUNC_ARE_INLINED) /* Copy C bytes from S to D. * Only works if non-overlapping, or if D < S. */ EXTERN_C void * __cdecl memcpy(void *d, const void *s, size_t c) { if ((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1)) { BYTE *pS = (BYTE *) s; BYTE *pD = (BYTE *) d; BYTE *pE = (BYTE *) (((ADDRESS) s) + c); while (pS != pE) *(pD++) = *(pS++); } else { UINT *pS = (UINT *) s; UINT *pD = (UINT *) d; UINT *pE = (UINT *) (BYTE *) (((ADDRESS) s) + c); while (pS != pE) *(pD++) = *(pS++); } return d; } #endif /* ! __MEMFUNC_ARE_INLINED */ #endif /* ! __MACHDEP_MEMFUNC */ 

代码正在测试地址是否适合UINT 。 如果是,则代码使用UINT对象进行复制。 如果不是,则代码使用BYTE对象进行复制。

测试首先执行两个地址的按位OR。 任何一个地址中的任何位都将在结果中打开。 然后测试执行按位AND与sizeof(UINT) - 1 。 预计UINT的大小是2的幂。 然后,大小减去1的所有低位都打开。 例如,如果大小是4或8,那么小于4,或者是二进制11 2或111 2 。 如果任一地址不是UINT大小的倍数,那么它将打开其中一个位,测试将指示它。 (通常,整数对象的最佳对齐方式与其大小相同。这不一定正确。此代码的现代实现应使用_Alignof(UINT) - 1而不是大小。)

使用UINT对象复制更快,因为在硬件级别,一个加载或存储指令加载或存储UINT所有字节(可能是四个字节)。 处理器通常在使用这些指令时比使用四倍数量的单字节加载或存储指令更快地复制。

这段代码当然是依赖于实现的; 它需要来自C实现的支持,它不是基本C标准的一部分,它取决于它执行的处理器的特定function。

更高级的memcpy实现可以包含其他function,例如:

  • 如果其中一个地址已对齐但另一个未对齐,则使用特殊的加载未对齐指令从一个地址加载多个字节,并将常规存储指令加载到另一个地址。
  • 如果处理器具有单指令多数据指令,则使用这些指令在单个指令中加载或存储许多字节(通常为16,可能更多)。

代码

 ((((ADDRESS) s) | ((ADDRESS) d) | c) & (sizeof(UINT) - 1)) 

检查sdc是否与UINT的大小不对齐。

例如,如果s = 0x7ff30b14d = 0x7ffa81d8 ,c = 256sizeof(UINT) == 4 ,则:

 s = 0b1111111111100110000101100010100 d = 0b1111111111110101000000111011000 c = 0b0000000000000000000000100000000 s | d | c = 0b1111111111110111000101111011100 (s | d | c) & 3 = 0b00 

所以两个指针都是对齐的。 在两个对齐的指针之间复制内存更容易,而这仅使用一个分支。

在许多体系结构中,如果ptrUINT的宽度正确对齐, *(UINT *) ptr 快得多。 在某些体系结构上,如果ptr未正确对齐, *(UINT *) ptr实际上会崩溃。