【问题标题】:Understanding the source code of memcpy()理解 memcpy() 的源码
【发布时间】:2013-07-11 11:00:28
【问题描述】:
00018 void *memcpy(void *dst, const void *src, size_t len)
00019 {
00020         size_t i;
00021 
00022         /*
00023          * memcpy does not support overlapping buffers, so always do it
00024          * forwards. (Don't change this without adjusting memmove.)
00025          *
00026          * For speedy copying, optimize the common case where both pointers
00027          * and the length are word-aligned, and copy word-at-a-time instead
00028          * of byte-at-a-time. Otherwise, copy by bytes.
00029          *
00030          * The alignment logic below should be portable. We rely on
00031          * the compiler to be reasonably intelligent about optimizing
00032          * the divides and modulos out. Fortunately, it is.
00033          */
00034 
00035         if ((uintptr_t)dst % sizeof(long) == 0 &&
00036             (uintptr_t)src % sizeof(long) == 0 &&
00037             len % sizeof(long) == 0) {
00038 
00039                 long *d = dst;
00040                 const long *s = src;
00041 
00042                 for (i=0; i<len/sizeof(long); i++) {
00043                         d[i] = s[i];
00044                 }
00045         }
00046         else {
00047                 char *d = dst;
00048                 const char *s = src;
00049 
00050                 for (i=0; i<len; i++) {
00051                         d[i] = s[i];
00052                 }
00053         }
00054 
00055         return dst;
00056 }

我刚刚经历了memcpy 的实现,以了解它与使用循环有何不同。但是我看不出使用循环而不是memcpy 之间有什么区别,因为memcpy 在内部再次使用循环来复制。

我无法理解 if 他们为整数所做的部分——i &lt; len/sizeof(long)。为什么需要这个计算?

【问题讨论】:

  • 这段代码是从哪里来的?我见过更好的优化 memcpy 实现...
  • @Maxime:你怎么知道:你甚至不知道目标处理器(或编译器)!
  • @Angus,从您在*.com/questions/11772553/… 中的回答来看,您似乎了解对齐方式。 long 是一个处理器字,地址需要与处理器字对齐以获得更快的复制(大多数架构对对齐的数据执行更快的复制)。如果你做不到,那就慢慢做,一个字节一个字节。下面有很好的答案。
  • 这个函数不会破坏别名规则(通过不一定声明为 long 的 long* 访问内存),因此是未定义的行为吗?
  • @jcoder 实现的实现不必遵循任何规则。如果您将此代码复制到您自己的名称的函数中并以此方式使用它,那将是一种违规行为。

标签: c


【解决方案1】:

我不明白他们是否为整数做部分。 i

因为在这种情况下它们复制的是单词,而不是单个字节(正如评论所说,这是一种优化 - 它需要更少的迭代,并且 CPU 可以更有效地处理单词对齐的数据)。

len 是要复制的字节数sizeof(long)单个单词的大小,所以要复制的元素数(意味着,要执行的循环迭代)是len / sizeof(long)

【讨论】:

  • 字对齐副本实际上并不依赖于 CPU。这取决于 RAM 的接线方式。
  • @m0skit0 我会说这取决于架构 - 有些 CPU 甚至无法访问非对齐地址的数据,有些 CPU 可以访问所有地址的单个字节,但是这些访问可能比访问对齐的地址慢得多
  • 是的,我知道。正是因为 RAM 的接线方式 ;) 无论如何,这是一个细节。你的答案是正确的。
  • 可能应该在我的回答中说“架构”而不是“CPU”,就像你所做的那样:)
【解决方案2】:

了解它与使用循环有何不同。但我不能 使用循环而不是 memcpy 的区别,因为 memcpy 使用循环 再次在内部复制

那么它使用循环。也许 libc 的其他实现不会那样做。无论如何,如果它确实使用循环有什么问题/问题?此外,正如您所见,它不仅仅是一个循环:它检查对齐并根据对齐执行不同类型的循环。

我不明白他们是否为整数做部分。我

这是检查内存字对齐。如果目标地址和源地址是字对齐的,并且复制的长度是字大小的倍数,那么它会按字执行对齐复制 (long),这比使用字节 (char) 更快,不仅由于大小,还因为大多数架构执行字对齐副本的速度要快得多。

【讨论】:

  • 我使用循环所做的也是字对齐的(int -> 4 字节)。
  • 如果有条件,只值得做 4 个字节。否则字复制可能比字节复制慢。事实上,在某些架构中,您只能在字对齐的情况下复制内存区域,否则这是硬件异常(例如 MIPS IIRC)。
【解决方案3】:

len%sizeof(long) 检查您是否尝试复制不属于long 的完整长度。

00035    if ((uintptr_t)dst % sizeof(long) == 0 &&
00036             (uintptr_t)src % sizeof(long) == 0 &&
00037             len % sizeof(long) == 0) {
00038 
00039                 long *d = dst;
00040                 const long *s = src;
00041 
00042                 for (i=0; i<len/sizeof(long); i++) {
00043                         d[i] = s[i];
00044                 }

检查对齐,如果为真,则快速复制(一次sizeof(long) 个字节)。

00046    else {
00047                 char *d = dst;
00048                 const char *s = src;
00049 
00050                 for (i=0; i<len; i++) {
00051                         d[i] = s[i];
00052                 }
00053    }

这是针对未对齐的数组(慢速复制(一次 1 个字节))

【讨论】:

    【解决方案4】:
    for (i=0; i<len/sizeof(long); i++) {
        d[i] = s[i];
    }
    

    在这个for循环中,每复制一个long,总共要复制一个len,所以需要i&lt;len/sizeof(long)作为终止循环的条件。

    【讨论】:

      【解决方案5】:

      我刚刚经历了memcpy 的实现,以了解它与使用循环有何不同。但是我看不出使用循环而不是 memcpy 之间有什么区别,因为memcpy 在内部再次使用循环进行复制。

      循环(控制语句)是与 if(决策语句)相邻的基本元素之一,其他类似的东西很少。所以这里的问题不是关于正常循环和使用memcpy有什么区别。

      memcpy 只是通过为您提供随时可用的 API 调用来帮助您完成任务,而不是让您为一件小事编写 20 行代码。如果您愿意,您可以选择编写自己的代码来为您提供相同的功能。

      前面已经指出的第二点是,它在long 数据类型和其他类型之间提供了优化。因为在long 中,它复制一个数据块,我们称之为一个字,而不是逐字节复制,这需要更长的时间。在长的情况下,需要 8 次迭代才能完成的相同操作memcpy通过一次复制单词在一次迭代中完成

      【讨论】:

        【解决方案6】:

        就像你看到 memcpy 的汇编代码一样,它表明在 32 位系统中每个寄存器是 32 位的,它一次可以存储 4 个字节,如果你只复制 32 位寄存器中的一个字节,CPU 需要额外的指令周期。

        如果 len/count 是 4 的倍数,我们可以在一个周期内复制 4 个字节

            MOV FROM, R2
            MOV TO,   R3
            MOV R2,   R4
            ADD LEN,  R4
        CP: MOV (R2+), (R3+) ; "(Rx+)" means "*Rx++" in C
            CMP R2, R4
            BNE CP
        

        【讨论】: