【问题标题】:Implementing own memcpy (size in bytes?)实现自己的 memcpy(以字节为单位的大小?)
【发布时间】:2012-08-06 06:17:29
【问题描述】:

我最近有一个面试问题,我必须实现 memcpy。根据我的经验,我使用过很多 memcpy,所以这似乎不是一个棘手的问题。

所以,我开始实现一个循环,从一个指针到另一个指针一次复制一个地址,如下所示:

void memcpy(void* dest, void* src, int size){
    for(int index = 0; index < size; index++){
        dest[index] = src[index];
    }
}

然而,面试官打断了他们注意到 memcpy 的手册页说它“将 n 个字节从 src 复制到 dest”(我稍后确认)然后希望我通过 size/4 进行迭代,然后用另一个索引

嗯,这看起来很奇怪,因为我多年来一直使用 memcpy 没有问题而不必给它一个 *4 修饰符)。当我回到家时,我启动了 gdb 并复制了一个小字符串“hello”,并小心地使用 strlen() 和常量输入大小以查看它在哪里开始和停止。

    char* src = "hello";
    char* dest = calloc(16, sizeof(char));
    int len = strlen(src);
    memcpy(dest, src, len); // both my version and official version

现在我用 gdb 仔细检查了 src 和 dest,它们都包含“hello\0”。

所以我的问题是:我对使用数字 4(或“以字节为单位的大小”)有什么不理解?当这不是真正的行为时,为什么文档会说“n字节”?我在这里看不清楚什么?

【问题讨论】:

  • 旁注:您不能取消引用 void*。
  • 原型应该是void memcpy (void* dest, const void* src, size_t size);。如果它需要与 C 标准 memcpy() 兼容,它应该返回一个 void*。
  • @Lundin:小修正,我想你在这里打错了,原型应该是“void* memcpy (void* dest, const void* src, size_t size);”
  • @Rndp13 阅读整个评论。挑剔一点,标准的C原型是void* memcpy (void*restrict s1, const void*restrict s2, size_t n);

标签: c++ c


【解决方案1】:

他们要求您优化您的实现,并让它在循环内一次复制一个 32 位字,而不是一次复制一个字节。这需要仔细检查以处理边界情况,例如 size 不是 4 的倍数,或者 dest 或 src 未在 4 字节边界上对齐。

【讨论】:

  • 但是“dest[index] = src[index];”不只是复制一个字节。我意识到 void* 不能是延迟,所以假设 src 和 dest 是 int*。该操作不会复制一个字节,它会复制整数的所有 32 位。一台 32bit 的机器,每个地址可以有 32bits 的数据,对吗?我不明白。
  • 次要挑剔:dest 和 src 不必是字对齐的。它们可以错位相同的量,当然您必须对前几个字节和最后几个字节进行特殊处理。
  • @HCHogan:由于您将类型声明为void*,大多数阅读您的问题的人都认为您的意思是char*,这是天真的mem​​cpy的通常选择。这就是 John 建议您一次复制一个字节的原因。
【解决方案2】:

他们希望你加快速度。 32 位处理器复制 32 位的速度比复制 8 位的速度快。因此,如果有人想要复制 4 个字节而不是一次复制一个,那么您可以一次完成所有操作。

【讨论】:

  • 但是“dest[index] = src[index];”复制整个寄存器,而不仅仅是一个字节。 (假设我将函数签名更改为 int* 而不是 void*)。
  • 但是你的循环是错误的,复制了你想要的字节数的 4 倍。他们忽略了类型问题(因为这很容易解决,而且不是他们想在面试中从你那里找到的......)
【解决方案3】:

你的 memcpy 的逻辑是正确的,你的面试官没有要求你改变它或添加限制。一次复制 4 个字节会更快,但如果你的大小不是 4 的倍数,就会出现问题。因此面试官告诉你使用两个循环:第一个循环一次复制 4 个字节,第二个循环一个字节一次时间(最多迭代 3 次)。

所以复制的大部分是用快速的 4 字节复制完成的,但你不限于大小为 4 的倍数,因为第二个“清理”循环将复制任何不是 4 的倍数的东西。

第一个循环:复制 uint32_t 并递增 4
第二个循环:复制 uint8_t 并递增 1

【讨论】:

  • 即使要复制的总数据是 4 字节的倍数,也可能会出现问题。第一个指针可能指向 4N+1 形式的字节地址,第二个指针可能指向 4N+2 形式的字节地址;在这种情况下,您不能轻松地以 4 个字节的倍数进行复制。
【解决方案4】:

面试官正在测试您的计算机架构知识,并希望您优化算法。内存操作的是字而不是字节。在 32 位系统中,一个字通常是 4 个字节,读/写 1 个字节所用的时间与读/写 1 个字所用的时间相同。第二个循环是处理要复制的字节数不能被 4 个字节整除的情况。

你真正想要的是 3 个循环。 2 循环用于 dest 之后和 dest+size 之前的字节,当两者都不是字对齐时。然后对中间的所有单词进行另一个循环。

您实际上可以通过利用特定于架构的指令进行更多优化。感兴趣的可以看看这篇文章:http://www.eetimes.com/design/embedded/4024961/Optimizing-Memcpy-improves-speed

【讨论】:

    【解决方案5】:

    正如其他人所说,一次复制 4 个字节比一次复制 1 个字节要快。面试官希望你这样做:

    void memcpy(void* dest, void* src, int size)
    {
        uint8_t *pdest = (uint8_t*) dest;
        uint8_t *psrc = (uint8_t*) src;
    
        int loops = (size / sizeof(uint32_t));
        for(int index = 0; index < loops; ++index)
        {
            *((uint32_t*)pdest) = *((uint32_t*)psrc);
            pdest += sizeof(uint32_t);
            psrc += sizeof(uint32_t);
        }
    
        loops = (size % sizeof(uint32_t));
        for (int index = 0; index < loops; ++index)
        {
            *pdest = *psrc;
            ++pdest;
            ++psrc;
        }
    }
    

    【讨论】:

    • 感谢您的演示。我现在知道了。我已经接受了这样一个事实,即我误解了内存的布局方式。我认为每个单独的地址都能够存储整个单词,而不仅仅是一个字节(例如地址 0x12345678 可以保存一个值 0xffffffff,然后 0x12345679 可以保存一个完全不同的值 0x00000000)。现在我意识到 0x12345678 只包含一个单词的一个字节,下一个单词将从 0x12345678 + 4 开始。这可以在 gdb 中使用 x 命令轻松看到。
    • @Rey Lebeau 请问复制4个字节比复制一个字节快的原因是什么?假设是 32 位系统。我想它节省了在单字节情况下对齐每个字中每个字节的时间?另外,在复制剩余字节时,是否不需要将指针转换回 char* 以一次复制一个字节? *pdest = *psrc; ++pdest; ++psrc;
    • 别介意我的第二个问题,没有看到 void* 指针已经转换为 char*。
    • uint8_t 和 uint32_t 在 #include &lt;stdint.h&gt; 中定义。 source
    • @Accountantم 在 C 中,是的。在 C++ 中,请改用 &lt;cstdint&gt;
    【解决方案6】:

    出于某种原因,面试官要求您执行过早的优化。这通常是个坏主意。

    确实,32 位机器复制一个 32 位卡盘比复制 4x1 字节快。但优化远不止这些。

    32 位机器很有可能将您的数据放入高速缓存内存中,然后突然快速的内存访问可能比 CPU 指令更相关。高速缓存存储器可能有各种对齐要求。他们可能更喜欢普通的循环,或者他们可能更喜欢 32 位对齐的块。我不是这方面的专家,所以我避免过早优化并将其留给编译器,希望编译器比我更了解缓存。

    然后是 CPU 分支预测和指令管道。这个特定的代码是相当确定的,所以这可能不是问题。但作为经验法则:简单代码比复杂代码产生更有效的分支预测。

    此外,还有除法,这在许多 CPU 架构上很慢。根据要复制的数据量,划分可能会导致 memcpy 慢得多。

    总结一下:手动优化相当复杂,需要对 CPU 和硬件有深入的了解。您不能也不应该“针对 32 位 CPU 进行优化”,您需要了解具体情况。在大多数情况下,编译器会比您更有效地优化代码。特别是 memcpy() 库,通常是用内联汇编程序编写的,针对特定目标进行了优化。

    【讨论】:

    • 同样,有足够经验的人会知道缓存也是对齐的,而且边界更粗。 16 字节是一个常见的值。 “师”同样是一条红鲱鱼。除以 2 的恒定幂对于所有意图和目的而言都是免费的,因为它是位移 (&gt;&gt;2)。
    【解决方案7】:

    看看这个..

    void myMemCpy(void *dest, void *src, size_t n)
    {
       // Typecast src and dest addresses to (char *)
       char *csrc = (char *)src;
       char *cdest = (char *)dest;
    
       // Copy contents of src[] to dest[]
       for (int i=0; i<n; i++)
           cdest[i] = csrc[i];
    }
    

    For more info

    【讨论】:

      猜你喜欢
      • 2013-05-03
      • 1970-01-01
      • 2013-02-01
      • 1970-01-01
      • 2014-01-27
      • 2016-03-19
      • 1970-01-01
      • 2021-09-22
      • 1970-01-01
      相关资源
      最近更新 更多