C++:memcpy vs. memmove:性能与安全的博弈,浅谈POD类型
在C++中,memcpy和memmove都用于内存块的二进制复制,但它们在处理内存重叠时的行为有显著差异。memcpy假设源和目标内存块不重叠,若强行复制重叠区域,会导致数据被覆盖,结果不可预测。例如,将数组的一部分复制到其自身时,memcpy可能破坏原始数据。相比之下,memmove通过检查源和目标地址的相对位置,选择从前往后或从后向前复制,确保数据安全。memmove适用于内存可能重叠的场景,
前言:
在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优化性能。
更多推荐


所有评论(0)