C语言: 实现memmove时的内存重叠处理

0. 概要

在C语言中,memmovememcpy都用于在内存中复制数据。然而,它们有一个关键的区别:memmove能够安全地处理源地址(src)和目标地址(dest)之间的内存重叠问题,而memcpy不能。在这篇博客中,我们将深入分析memmove的实现,探讨它如何处理内存重叠,并展示详细的内存操作过程。

1. memmove的实现代码

首先,来看一下memmove的实现代码:

void *my_memmove(void *dest, const void *src, size_t n) {
    uint8_t *d = (uint8_t *)dest;
    const uint8_t *s = (const uint8_t *)src;

    if (d == s) {
        return dest;
    }

    if (d < s) {
        // 如果目标地址在源地址之前,直接从前往后复制
        for (size_t i = 0; i < n; i++) {
            d[i] = s[i];
        }
    } else {
        // 如果目标地址在源地址之后,从后往前复制,避免重叠问题
        for (size_t i = n; i != 0; i--) {
            d[i - 1] = s[i - 1];
        }
    }

    return dest;
}

2. 前往后复制的安全性

2.1 情景一:目标地址在源地址之前

假设目标地址在源地址的前面,内存布局如下图所示:

内存地址(从低到高):
    低地址  ->  | dest   | src    | ... |  <-  高地址

在这种情况下,从前往后复制是安全的。每次从src中读取数据并写入dest时,dest的地址总是在src之前,因此不会覆盖尚未复制的数据。

2.2 图示解释

假设源地址src的起始地址是0x100,目标地址dest的起始地址是0x080,数据长度n = 5

原始内存布局:

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  |      |      |      |      |      |      | src[0] | src[1] | src[2] | src[3] | src[4] |

第一步:复制src[0]dest[0]

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  src[0] |      |      |      |      |      | src[0] | src[1] | src[2] | src[3] | src[4] |

第二步:复制src[1]dest[1]

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  src[0] | src[1] |      |      |      |      | src[0] | src[1] | src[2] | src[3] | src[4] |

第三步:复制src[2]dest[2]

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  src[0] | src[1] | src[2] |      |      |      | src[0] | src[1] | src[2] | src[3] | src[4] |

第四步:复制src[3]dest[3]

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  src[0] | src[1] | src[2] | src[3] |      |      | src[0] | src[1] | src[2] | src[3] | src[4] |

第五步:复制src[4]dest[4]

地址     ->  0x080  0x081  0x082  0x083  0x084  0x085  0x086  0x087  0x088  0x089  0x08A  0x08B
内容     ->  src[0] | src[1] | src[2] | src[3] | src[4] |      | src[0] | src[1] | src[2] | src[3] | src[4] |

在上述步骤中,由于目标地址总是在源地址之前,所以从前往后复制是安全的,不会出现覆盖问题。

3 从前往后复制可能导致的覆盖问题

3.1 情景二:目标地址在源地址之后

假设目标地址在源地址的后面,内存布局如下图所示:

内存地址(从低到高):
    低地址  ->  | src    | dest   | ... |  <-  高地址

在这种情况下,如果仍然从前往后复制,可能会导致源数据被覆盖。

3.2 图示解释

假设源地址src的起始地址是0x100,目标地址dest的起始地址是0x102,数据长度n = 5

原始内存布局:

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[2] src[3] src[4] ...

第一步:复制src[0]dest[0]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[0] src[3] src[4] ...

第二步:复制src[1]dest[1]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[0] src[1] src[4] ...

第三步:复制src[2]dest[2]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[0] src[1] src[0] ...

在这个过程中,src[2]的内容已经在第二步时被覆盖,因此在第三步时无法正确复制。这就导致了数据的损失或混乱。

4. 从后往前复制的安全性

4.1 图示解释

为了避免上述覆盖问题,我们可以从后往前复制数据。

假设源地址src的起始地址是0x100,目标地址dest的起始地址是0x102,数据长度n = 5

原始内存布局:

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0



] src[1] src[2] src[3] src[4] ...

第一步:复制src[4]dest[4]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[2] src[3] src[4] src[4]

第二步:复制src[3]dest[3]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[2] src[3] src[3] src[4]

第三步:复制src[2]dest[2]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[2] src[2] src[3] src[4]

第四步:复制src[1]dest[1]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[1] src[1] src[2] src[3] src[4]

第五步:复制src[0]dest[0]

地址     ->  0x100  0x101  0x102  0x103  0x104  0x105  0x106
内容     ->  src[0] src[0] src[1] src[2] src[3] src[4]

通过从后往前复制,我们避免了对尚未复制的源数据的覆盖,从而保证了数据的完整性。

5. 结论

在实现memmove时,关键是要正确处理源地址和目标地址之间的内存重叠问题。如果目标地址在源地址之前,可以安全地从前往后复制;而如果目标地址在源地址之后,则需要从后往前复制,以避免数据覆盖问题。通过这种方式,我们可以确保memmove函数的安全性和正确性。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值