【问题标题】:Understanding the implementation of memcpy()了解 memcpy() 的实现
【发布时间】:2013-10-11 19:49:34
【问题描述】:

我正在寻找 memcpy.c 的实现,我发现了一个不同的 memcpy 代码。我不明白他们为什么这样做 (((ADDRESS) 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 */

【问题讨论】:

  • 这就是为什么你不想自己写memcpy :-) 真正的美只有在你查看生成的机器代码时才能看到。

标签: c memcpy language-implementation


【解决方案1】:

代码正在测试地址是否适合UINT。如果是这样,代码将使用UINT 对象进行复制。如果没有,代码将使用BYTE 对象进行复制。

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

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

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

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

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

【讨论】:

    【解决方案2】:

    代码

    ((((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
    

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

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

    【讨论】:

    • 它也检查长度。
    • 哇,简洁而不是清晰。任何需要四组括号的代码都应该重新考虑。
    • @EricAndres 欢迎来到性能世界。这种东西到处都是。 :)
    • @Mysticial 我敢肯定。我不是 C 程序员,所以我倾向于很容易地忽略这样的事情。这绝对是一段​​可以证明微优化的代码。
    • @EricAndres 但是括号并不重要,当然。理想情况下,你会看到((ADDRESS) s)((ADDRESS) d) 是不透明的块SD,所以你有(S | D | c) &amp; (sizeof(UNIT) - 1),这还不错,然后你'从if ( … ) … 得到了另外一套。表达式本身并没有以特别复杂的方式嵌套。
    猜你喜欢
    • 2023-03-21
    • 1970-01-01
    • 2015-10-18
    • 2013-10-12
    • 2018-12-31
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 2021-07-16
    相关资源
    最近更新 更多