1. memcpy使用和模拟实现
2. memmove使用和模拟实现
3. memset函数的使用
4. memcmp函数的使用
上文我们讲了字符串函数strcpy ,这个函数的局限性太大,只能用来拷贝字符串
今天我们学习的内存函数 就可以实现任意类型的拷贝,大大提高了实用性
1. memcpy使用和模拟实现
void * memcpy ( void * destination, const void * source, size_t num );
功能:
memcpy 是完成内存块拷贝的,不关注内存中存放的数据是什么
函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存
位置。
如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。
!!!(内存重叠的情况使用 memmove 就行)
参数:
destination :指针,指向目标空间,拷贝的数据存放在这里
source :指针,指向源空间,要拷贝的数据从这里来
num :要拷贝的数据占据的字节数
返回值:
拷贝完成后,返回目标空间的起始地址
1.1代码演示
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = { 0 };
// 拷贝 20 个字节 = 5 个 int 元素(1,2,3,4,5)
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
运行结果:
1 2 3 4 5 0 0 0 0 0
!!!这里 num 传的是 20,因为 int 占 4 字节,5 个元素就是 5*4=20 字节。
1.2memcpy手动模拟实现
void * memcpy ( void * dst, const void * src, size_t count)
{
void * ret = dst; // 保存目标起始地址,用于最后返回
assert(dst); // 断言:目标指针不能为空
assert(src); // 断言:源指针不能为空
while (count–) {
*(char *)dst = *(char *)src; // 按字节拷贝(char* 是 1 字节)
dst = (char *)dst + 1; // 目标指针向后移动 1 字节
src = (char *)src + 1; // 源指针向后移动 1 字节
}
return ret;
}
解析:
为什么用 char* 转换?
因为 memcpy 是按字节拷贝的,char* 每次移动 1 字节,刚好对应最小拷贝单位。
为什么要保存 ret?
因为循环中 dst 会被不断修改,最后需要返回原始的目标起始地址。
2. memmove使用和模拟实现
void * memmove ( void * destination, const void * source, size_t num );
功能:
memmove函数也是完成内存块拷贝的
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
memmove的使用需要包含<string.h>
参数:
destination :指针,指向目标空间,拷贝的数据存放在这里
source :指针,指向源空间,要拷贝的数据从这里来
num :要拷贝的数据占据的字节数
返回值:
拷贝完成后,返回目标空间的起始地址
2.1代码演示
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20); // 从 arr1 起始位置拷贝 20 字节到 arr1+2
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
分析:
arr1 是 int 数组,每个元素占 4 字节,20 字节对应 5 个元素。
arr1 + 2 表示目标地址是数组第 3 个元素(索引 2)的位置。
源地址是 arr1(索引 0),目标地址 arr1+2(索引 2),两者内存是重叠的。
memmove 会安全地处理这种重叠,先把源数据完整保存再拷贝,不会覆盖还没复制的数据。
运行结果:
1 2 1 2 3 4 5 8 9 10
2.1memove手动模拟实现
void * memmove ( void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count)) {
// 情况1:无重叠,或者目标在源前面,按从低地址到高地址的顺序拷贝
while (count–) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
} else {
// 情况2:有重叠,且目标在源的后面,从高地址到低地址倒序拷贝
dst = (char *)dst + count – 1;
src = (char *)src + count – 1;
while (count–) {
*(char *)dst = *(char *)src;
dst = (char *)dst – 1;
src = (char *)src – 1;
}
}
return(ret);
}
1.先判断是否重叠,以及重叠的方向
if 条件:dst<= src(目标地址在源前面) 或 dst >= src+count(目标地址在源的拷贝范围之后)→ 无重叠,按 memcpy 的方式从前向后拷贝即可。
else 条件:目标地址落在源的拷贝范围内,且在源的后面 → 从后向前倒序拷贝,避免覆盖还没复制的数据。
2.为什么要分两种拷贝方向?
正序拷贝:适用于无重叠场景,高效简单。
倒序拷贝:专门解决目标在源后面的重叠场景,先复制后面的数据,再复制前面的数据,保证源数据不被提前覆盖
2.3memcpy与memmove对比
| 特性 | memcpy | memmove |
| 处理内存重叠 | 不支持,结果未定义 | 支持,结果安全 |
| 拷贝方向 | 固定从前向后 | 根据重叠情况,自动选择正序 / 倒序 |
| 适用场景 | 源和目标内存完全独立 | 源和目标可能重叠 |
| 效率 | 略高(无需判断重叠) | 略低(多了一次判断逻辑) |
3.memset函数的使用
void * memset ( void * ptr, int value, size_t num );
功能:
memset 函数是用来设置内存块的内容的,将内存中指定长度的空间设置为特定的内容。
memset 的使用需要包含 <string.h>
参数:
ptr :指针,指向要设置的内存空间,也就是存放了要设置的内存空间的起始地址。
value :要设置的值,函数将会把 value 值转换成 unsigned char 的数据进行设置的。 也就是以字节为单位来设置内存块的。
num :要设置的内存长度,单位是字节。
返回值:返回的是要设置的内存空间的起始地址。
3.1代码演示
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf(str);
return 0;
}
运行结果:
xxxxxxworld
分析:
str 初始内容:hello world(注意 hello 后有一个空格,共 6 个字符)
memset(str, 'x', 6) 会从 str 起始地址开始,将前 6 个字节(hello)全部替换为 'x'
因此输出的前 6 个字符变为 xxxxxx,后面的 world 保持不变
3.2关键注意事项
1.按字节填充的本质memset 是单字节操作,因此:
对 char 数组填充字符(如 'x')是安全的;
对 int 数组填充时,会将每个字节都设为 value 对应的 unsigned char 值。例如:int arr[5]; memset(arr, 0, sizeof(arr)); 可以将数组清 0,但 memset(arr, 1, sizeof(arr)); 不会把每个 int 设为 1,而是设为 0x01010101(小端 / 大端下的多字节组合值)。
2.num 的单位是字节计算长度时要注意:
对数组 arr,推荐写法:memset(arr, 0, sizeof(arr));(自动计算总字节数)
避免硬编码错误,比如 int arr[10]; memset(arr, 0, 10); 只会清前 10 个字节(前 2 个 int)3.value 会被截断传入的 int 类型 value 会被截断为 unsigned char,因此传入 0x1234 实际效果和传入 0x34 是一样的。 0x1234 是一个 16 位的十六进制数,对应的二进制是:0001 0010 0011 0100 unsigned char 只能存 8 位,所以 memset 只会取它的低 8 位:0011 0100,也就是 0x34
4. memcmp函数的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
功能:
比较指定的两块内存块的内容,比较从ptr1和ptr2指针指向的位置开始,向后的num个字节
memcmp 的使用需要包含 <string.h>
参数:
ptr1 :指针,指向⼀块待比较的内存块
ptr2 :指针,指向另外⼀块待比较的内存块
num :指定的比较长度,单位是字节
返回值:

4.1代码演示
#include <stdio.h>
#include <string.h>
int main()
{
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n;
n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0)
printf("'%s' 大于 '%s'.\\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' 小于 '%s'.\\n", buffer1, buffer2);
else
printf("'%s' 和 '%s' 一样.\\n", buffer1, buffer2);
return 0;
}
分析:
我们逐字节对比 buffer1 和 buffer2:
前两个字符 'D' 'W' 完全相同
第三个字符:buffer1 是小写 'g'(ASCII 码 0x67),buffer2 是大写 'G'(ASCII 码 0x47)
因为 0x67 > 0x47,所以 memcmp 返回正数,程序会输出:'DWgaOtP12df0' 大于 'DWGAOTP12DF0'.
4.2关键细节
1.与strcmp对比
| 函数 | 比较方式 | 终止条件 | 适合场景 |
| memcmp | 按字节比较,固定长度 | 必须比较完 num 个字节,不受 '\\0' 影响 | 任意类型的内存块(如 int 数组、结构体) |
| strcmp | 按字符比较,字符串专用 | 遇到 '\\0' 就停止 | 仅用于比较 C 风格字符串 |
举个反例:如果字符串中间有 '\\0',strcmp 会直接截断比较,而 memcmp 会继续比较后续字节。
2.比较的是 unsigned char 值
memcmp 会把每个字节当成 unsigned char 来比较,所以有符号字符的正负不影响比较结果,只看无符号的数值大小。
3. num 的单位是字节
对数组比较时,推荐用 sizeof(数组) 自动计算长度,避免硬编码错误。
例如:int arr1[5], arr2[5]; memcmp(arr1, arr2, sizeof(arr1)); 可以安全比较两个 int 数组的全部字节。

