在 C 语言中,“块拷贝”通常指的是将一块内存区域(源数据)的内容完整地复制到另一块内存区域(目标地址)中,这和单个字符或整数的拷贝不同,它处理的是连续的、由多个字节组成的数据块。

(图片来源网络,侵删)
下面我们从几个方面来深入探讨这个主题:
- 最原始、最通用的方法:
memcpy - 带有重叠区域检测的方法:
memmove - 基于字符串的拷贝:
strcpy和strncpy - 手动实现块拷贝
- 总结与最佳实践
最原始、最通用的方法:memcpy
memcpy 是 C 标准库 <string.h> 中提供的函数,专门用于内存块的拷贝,它是进行块拷贝最常用、最高效的方法。
函数原型
#include <string.h> void *memcpy(void *dest, const void *src, size_t n);
参数说明
dest: 目标内存块的指针,拷贝的数据将被写入到这里。src: 源内存块的指针,数据从这里被读取。n: 要拷贝的字节数。size_t是一个无符号整数类型,足以表示内存中最大的可能对象。
返回值
- 返回
dest指针,即目标内存块的起始地址。
关键特性
- 高效:
memcpy的实现通常经过高度优化,会利用处理器的指令(如字、双字、四字拷贝)来一次性移动多个字节,而不是一个字节一个字节地移动,因此速度非常快。 - 不处理重叠:
memcpy假设源内存块和目标内存块不重叠,如果它们重叠,拷贝的结果是未定义的(Undefined Behavior),可能会导致数据损坏。
示例代码
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20]; // 确保目标缓冲区足够大
// 拷贝 13 个字节(包括 '\0' 结尾符)
memcpy(dest, src, 13);
printf("Source: %s\n", src);
printf("Destination: %s\n", dest);
// 拷贝整数数组
int int_src[] = {10, 20, 30, 40, 50};
int int_dest[5];
// 拷贝 5 个整数,每个整数 4 字节,共 20 字节
memcpy(int_dest, int_src, sizeof(int_src));
printf("\nInteger Source: ");
for (int i = 0; i < 5; i++) {
printf("%d ", int_src[i]);
}
printf("\nInteger Destination: ");
for (int i = 0; i < 5; i++) {
printf("%d ", int_dest[i]);
}
printf("\n");
return 0;
}
带有重叠区域检测的方法:memmove
当源内存块和目标内存块可能重叠时,使用 memcpy 是危险的,这时,你应该使用 memmove。
函数原型
#include <string.h> void *memmove(void *dest, const void *src, size_t n);
参数和返回值
与 memcpy 完全相同。

(图片来源网络,侵删)
关键特性
- 处理重叠:
memmove是专门为处理内存重叠情况而设计的,它会采取额外的措施(从后往前拷贝或使用临时缓冲区)来确保即使源和目标区域重叠,数据也能被正确拷贝,不会损坏。 - 可能稍慢: 因为需要处理重叠的逻辑,
memmove的实现通常比memcpy稍微复杂和慢一点点,但在不重叠的情况下,很多编译器会将其优化为memcpy。
何时使用?
- 当你确定源和目标不重叠时,优先使用
memcpy(因为它可能更快)。 - 当你不确定它们是否重叠,或者明确知道它们会重叠时,必须使用
memmove。
示例代码(演示重叠)
#include <stdio.h>
#include <string.h>
int main() {
char data[] = "0123456789";
printf("Original: %s\n", data);
// 目标区域 '345' 与源区域 '456' 重叠
// 错误示范:使用 memcpy 结果未定义
// memcpy(data + 3, data + 1, 5); // 危险!
// 正确示范:使用 memmove
memmove(data + 3, data + 1, 5);
printf("After memmove: %s\n", data); // 预期输出: 012234569
// 解释:从 data+1 ('1') 开始拷贝 5 个字节 ('12345') 到 data+3 ('3')
// '1' 覆盖了 '3', '2' 覆盖了 '4', '3' 覆盖了 '5', '4' 覆盖了 '6', '5' 覆盖了 '7'
// 最终得到: 01 12 23 45 6 9
return 0;
}
基于字符串的拷贝:strcpy 和 strncpy
这些函数是专门为以 '\0' (空字符) 结尾的 C 风格字符串设计的,它们不是通用的块拷贝函数,但也是一种常见的拷贝形式。
strcpy (String Copy)
char *strcpy(char *dest, const char *src);
- 功能: 拷贝字符串
src到dest,直到遇到'\0'为止,并且会自动在dest的末尾添加'\0'。 - 危险: 它不会检查
dest缓冲区的大小,src比dest长,就会导致缓冲区溢出(Buffer Overflow),这是非常严重的安全漏洞。 - 返回值: 返回
dest指针。
strncpy (String Copy with Number)
char *strncpy(char *dest, const char *src, size_t n);
- 功能: 拷贝
src中最多n个字符到dest。 - 两个关键点:
src的长度小于n,dest的剩余部分会用'\0'填充。src的长度大于或等于n,dest的前n个字符会被拷贝,但不会自动添加'\0',这意味着dest可能不是一个合法的以'\0'结尾的字符串。
- 用途: 主要用于固定长度的缓冲区拷贝,但使用时需要非常小心,最好手动确保
dest以'\0'
与 memcpy 的区别
| 特性 | memcpy |
strcpy / strncpy |
|---|---|---|
| 任意数据块(整数、结构体、字符串等) | 仅限字符串(以 '\0'
| |
| 终止条件 | 拷贝固定的字节数 n |
遇到 '\0' 或达到 n 个字符 |
'\0' 处理 |
不关心 '\0',它只是一个普通字节 |
strcpy 自动添加,strncpy 不一定添加 |
| 安全 | 需要手动确保 dest 足够大 |
strcpy 不安全,strncpy 相对安全但需小心 |
手动实现块拷贝
理解了这些库函数的工作原理后,我们可以手动实现一个简单的 memcpy 来加深理解。
简单实现(不考虑重叠)
#include <stddef.h> // for size_t
void* my_memcpy(void* dest, const void* src, size_t n) {
// 将 void* 指针转换为 unsigned char* 指针,以便进行字节级别的操作
unsigned char* d = (unsigned char*)dest;
const unsigned char* s = (const unsigned char*)src;
// 逐个字节拷贝
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
return dest;
}
考虑重叠的实现(模拟 memmove)
一个简单的方法是判断方向,如果是从后往前拷贝,则反向循环。
#include <stddef.h> // for size_t
void* my_memmove(void* dest, const void* src, size_t n) {
unsigned char* d = (unsigned char*)dest;
const unsigned char* s = (const unsigned char*)src;
// 判断是否重叠以及拷贝方向
if (d < s) {
// 情况1:dest在src之前,可以从前向后拷贝
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
} else if (d > s) {
// 情况2:dest在src之后,必须从后向前拷贝
for (size_t i = n; i > 0; i--) {
d[i - 1] = s[i - 1];
}
}
// 情况3:d == s,无需拷贝
return dest;
}
总结与最佳实践
| 函数 | 描述 | 何时使用 |
|---|---|---|
memcpy |
通用、快速的内存块拷贝。不处理重叠。 | 当源和目标不重叠时,首选,性能最高。 |
memmove |
安全的内存块拷贝,可以处理重叠。 | 当源和目标可能重叠时,必须使用。 |
strcpy |
字符串拷贝,直到 '\0'。不检查缓冲区大小。 |
不推荐使用,除非你能 100% 保证源字符串长度小于目标缓冲区。 |
strncpy |
字符串拷贝,最多拷贝 n 个字符。不一定以 '\0'。 |
用于固定长度字符串拷贝,但使用后务必检查并手动添加 '\0'。 |
核心建议:

(图片来源网络,侵删)
- 优先使用标准库函数:
memcpy和memmove是经过严格测试和高度优化的,比自己写的版本更可靠、更快。 - 明确拷贝场景:拷贝前,先问自己:“源内存和目标内存会重叠吗?”
- 不会 -> 使用
memcpy。 - 会/不确定 -> 使用
memmove。
- 不会 -> 使用
- 永远注意缓冲区大小:无论是
memcpy还是strncpy,都要确保目标缓冲区足够大,足以容纳你要拷贝的所有数据,包括结尾的'\0'。 - 避免
strcpy:在现代 C 编程中,strcpy因其不安全性而声名狼藉,应优先考虑使用strncpy并小心处理,或者使用更安全的替代方案(如 C++ 的std::string或 C 的snprintf)。
