前言:

在C++中,memcpy和memmove都是用于内存块的二进制复制的底层函数,但它们在处理内存重叠时的行为有本质区别。理解它们的差异需要结合内存操作的基本原理和实际场景。

1. memcpy为什么不安全?
memcpy的设计目标是高性能的内存复制,但它的实现有一个关键假设:源内存块和目标内存块不重叠。  
如果强行用memcpy复制重叠的内存区域,会导致不可预测的结果,具体问题如下:

场景示例:
假设有一个数组 char buffer[] = "ABCDEFGH";,现在需要将 buffer+2 开始的4字节(即"CDEF")复制到 buffer 起始位置:

char buffer[] = "ABCDEFGH";
memcpy(buffer, buffer + 2, 4);  // 重叠的复制!

预期结果:我们希望数组变为 "CDEFEFGH"。  
实际结果:由于memcpy从前向后逐字节复制,复制过程会覆盖源数据:
1. 第1步:将 C 复制到 buffer[0] → "CBCDEFGH"
2. 第2步:将 D 复制到 buffer[1] → "CDCDEFGH"
3. 第3步:将 E 复制到 buffer[2] → "CDEDEFGH"
4. 第4步:将 F 复制到 buffer[3] → "CDEFEFGH"

虽然最终结果符合预期,但中间的覆盖过程可能破坏原始数据。  
更严重的是,如果目标地址在源地址之后(例如将buffer复制到buffer+2),memcpy会导致数据被覆盖,最终结果完全错误:

char buffer[] = "ABCDEFGH";
memcpy(buffer + 2, buffer, 4);  // 目标地址在源地址之后!


实际结果:
1. 第1步:将 A 复制到 buffer[2] → "ABADEFGH"
2. 第2步:将 B 复制到 buffer[3] → "ABABEFGH"
3. 第3步:将 A 复制到 buffer[4] → "ABABAFGH"(后续步骤继续破坏数据)

最终结果为 "ABABABAB",完全不符合预期。

注意:如果你用memcpy在你的编译器测试可能和文章的不同,具体实现得看编译器。

2. memmove如何保证安全?
memmove的设计目标是安全处理内存重叠。它在复制前会检查源地址和目标地址的相对位置,并选择复制方向
- 如果目标地址在源地址之前(或两者不重叠),则从前往后复制(与memcpy相同)。
- 如果目标地址在源地址之后,则从后向前复制,避免覆盖源数据。

修正示例:

char buffer[] = "ABCDEFGH";
memmove(buffer + 2, buffer, 4);  // 使用 memmove


复制过程:
1. 检查发现目标地址在源地址之后,选择从后向前复制。
2. 第4步:将 D 复制到 buffer[5] → "ABCDEDGH"
3. 第3步:将 C 复制到 buffer[4] → "ABCDCDGH"
4. 第2步:将 B 复制到 buffer[3] → "ABCBDGH"
5. 第1步:将 A 复制到 buffer[2] → "ABABDGH"

最终结果为 "ABABABGH"(符合预期)。

3. 与POD类型的关系
POD类型(平凡且标准布局)确保了内存布局的确定性和二进制兼容性,使得memcpy/memmove可以安全地操作其内存。但这并不解决内存重叠问题
- POD类型:仅保证内存布局可预测,但若内存区域重叠,仍需用memmove
- 非POD类型:即使内存不重叠,也可能因虚表指针、资源句柄等特殊成员的存在,直接使用memcpy/memmove导致未定义行为。

总结

函数 内存重叠处理 性能 适用场景
memcpy 不检查,要求不重叠 已知内存不重叠的快速复制
memmove 检查并处理重叠 稍低 内存可能重叠的安全复制

- 关键区别memmove通过方向选择避免数据覆盖,而memcpy为追求性能不做检查。
- 安全实践:在不确定内存是否重叠时,优先使用memmove;仅在确保不重叠时用memcpy优化性能。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐