3

我为 memcpy 找到了以下实现(面试问题,其中迭代计数 ~ size/4):

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;
    }
}

而且我不确定我是否理解它.....:

1)为什么要定义uint8_t *pdest,uint8_t *psrc并在此之后进行强制转换uint32_t-

*((uint32_t*)pdest) = *((uint32_t*)psrc);

我认为从一开始就pdest应该psrc定义为 uint32_t ...我错过了什么?2)在我看来,这个实现存在问题:如果src = 0x100dst = 0x104 src(最初)看起来像这样:

-------------------------
|  6  |  8  |  7  |  1  |
-------------------------    
0x100  0x104 0x108 0x1C0

执行后会是这样

-------------------------
|  6  |  6  |  6  |  6  |.....
-------------------------
0x100  0x104 0x108 0x1C0

尽管看起来下面的内存布局应该是结果

-------------------------
|  6  |  6  |  8  |  7  |....
-------------------------
0x100  0x104 0x108 0x1C0
4

3 回答 3

7

memcpy()遇到了另一个问题:如果一个或两个缓冲区不在适当的边界上会发生什么?这可能会显着影响性能,或者在某些架构上,甚至会使代码无法运行。另一个常见问题(但不是这里)是处理长度不是本机 (uint32) 类型宽度的倍数的缓冲区。您的示例使用 uint8 类型(然后根据需要进行转换)的原因是允许复制尾随字节而不进行转换。如果您转换大部分传输或仅转换尾随字节,这没有区别。为了考虑缓冲区对齐,您会期望在建立对齐之前早期复制初始未对齐数据。

当源和目标重叠时,不能保证该memcpy()函数以定义的方式工作;因此,您标记为第二的问题不是问题。如果memcpy()在 的实现中使用此代码而不是memmove(),那么问题将是真实的。

于 2013-11-12T16:03:33.163 回答
0

关于指针类型:这里的想法是,为了减少循环和复制开销,您希望使用可能的最大数据“块”(比如 32 位)进行复制。因此,您尝试尽可能多地使用 32 位字进行复制。然后需要将其余部分复制到较小的 8 位“块”中。例如,如果您想复制 13 个字节,您将执行 3 次迭代复制 32 位字 + 1 次迭代复制单个字节。这比进行 13 次单字节复制迭代更可取。您可以转换为 uint32_t*,但随后您必须转换回 uint8_t* 才能完成剩余部分。

关于第二个问题 - 如果目标地址与源缓冲区重叠,此实现将无法正常工作。假设您也想支持这种 memcpy - 这是一个错误。这是一个流行的面试问题陷阱;)。

于 2013-11-12T16:09:07.950 回答
0

第一个循环通过并以每个循环 4 字节的块将内存从 psrc 复制到 pdest,因此强制转换为 uint32_t*。第二个循环以 1 字节的块复制剩余的内存。对于大块内存,这有效地将迭代次数减少了 4 倍。

至于强制转换为 uint8_t* 而不是 uint32_t* 的原因。通过直接转换为 uint32_t*,第一个循环可以正常工作,但是您需要在每个循环中将指针增加 1 而不是 4。你会得到类似下面的东西

for(int index = 0; index < loops; ++index)
{
    *(pdest) = *(psrc); //no need for cast
    pdest++;            //increment by 1 not 4
    psrc++;
}

但是,对于第二个循环,您需要强制转换为 uint8_t*,并将指针增加 1/4。指针算法无法做到这一点,因此不可能这样做。

另一种思考方式: loops1:原始内存块中 4 字节块的数量 loops2:剩余的字节数

于 2013-11-12T16:09:10.870 回答