`memcpy` 和 `memmove` 都是內存塊拷貝函數,關鍵區別在於**源與目的內存區域重疊時的行為**。本文通過圖解三種重疊情況、標準庫規範、手寫實現代碼,徹底講透二者區別與底層原理。
| 特性 | memcpy | memmove |
|---|---|---|
| 重疊行為 | 未定義行為(可能數據損壞) | 保證正確(如同臨時緩衝區) |
| 性能 | 極快(常優化為向量指令) | 略慢(需檢查重疊/反向拷貝) |
| 適用場景 | 確知不重疊、性能關鍵路徑 | 可能重疊、通用安全拷貝 |
| 標準頭文件 | ` | ` |
假設 `src` 源區域,`dst` 目標區域,長度 `n`。
src: [S S S S S]
dst: [D D D D D]
→ `memcpy`、`memmove` 均正常,順序拷貝即可。
src: [S S S S S]
dst: [D D D D D]
→ **順序拷貝安全**。從低地址向高地址拷貝,`dst` 寫入的數據不會覆蓋 `src` 未讀取的數據。`memcpy`、`memmove` 均可用順序拷貝。
src: [S S S S S]
dst: [D D D D D]
→ **順序拷貝會破壞數據**。例如拷貝前 2 字節時,`dst[0]`、`dst[1]` 覆蓋了 `src[2]`、`src[3]`,導致後續讀取到已被修改的數據。
**解決方案**:**逆向拷貝**(從高地址向低地址)。`memmove` 內部檢測此情況並自動逆向拷貝;`memcpy` 不保證,屬 UB。
void* my_memcpy(void* dest, const void* src, size_t count) {
// 斷言檢查空指針(調試用)
assert(dest != NULL && src != NULL);
char* d = (char*)dest;
const char* s = (const char*)src;
// 字節級順序拷貝
while (count--) {
*d++ = *s++;
}
return dest; // 返回目的地址,支持鏈式調用
}
void* my_memmove(void* dest, const void* src, size_t count) {
assert(dest != NULL && src != NULL);
char* d = (char*)dest;
const char* s = (const char*)src;
if (s < d) {
// 情況3:src 在 dst 前,重疊風險 → 逆向拷貝
d += count - 1;
s += count - 1;
while (count--) {
*d-- = *s--;
}
} else {
// 情況1、2:不重疊或 dst 在 src 前 → 順序拷貝
while (count--) {
*d++ = *s++;
}
}
return dest;
}
現代 libc(glibc、MSVC CRT 等)對 `memcpy`/`memmove` 高度優化:
`memcpy` 假設不重疊,單向拷貝極快;`memmove` 檢測重疊,智能選擇方向,保證語義正確。理解「地址大小關係決定拷貝方向」是掌握內存拷貝本質的關鍵。手寫實現僅 20 行,卻蘊含指針算術、內存模型、UB 避免等核心 C 語言功力。