C 语言的内存函数:memcpy/memmove/memset/memcmp 精讲(含模拟实现)
本文聚焦 C 语言四大核心内存操作函数,详解memcpy(非重叠内存拷贝)与memmove(支持重叠拷贝)的用法及模拟实现逻辑,对比两者核心差异;拆解memset按字节设置内存的特性与易错点,以及memcmp逐字节比较内存的规则。内容覆盖内存操作全场景,助力夯实底层编程能力。

🏠个人主页:黎雁
🎬作者简介:C/C++/JAVA后端开发学习者
❄️个人专栏:C语言、数据结构(C语言)、EasyX、游戏、规划
✨ 从来绝巘须孤往,万里同尘即玉京

文章目录
字符串专题收官之后,我们迎来C语言内存操作的核心内容!这一篇聚焦四大内存函数——memcpy(内存拷贝)、memmove(内存移动)、memset(内存设置)、memcmp(内存比较),详解它们的使用规则、核心差异和模拟实现逻辑,帮你吃透以字节为单位的内存操作底层原理!
前景回顾:字符串函数核心速记 📝
C 语言字符串入门:字符函数 + strlen 精讲(从使用到模拟实现)
C 语言字符串进阶:strcpy/strcat/strcmp 精讲
C 语言字符串高阶:strstr/strtok/strerror 精讲(含 strstr 模拟实现)
回顾字符串函数的核心特性,能帮我们更好理解内存函数的设计逻辑:
- 字符串函数依赖
\0作为终止标志,仅能处理字符类型数据。 - 内存函数不关心数据类型,以字节为单位操作内存,通用性更强。
- 模拟实现库函数时,
assert断言指针非空是保证代码健壮性的必备操作。
一、memcpy:不重叠内存的拷贝神器 📤
memcpy的全称是 memory copy,作用是从源内存地址拷贝指定字节数的数据到目标内存地址,和字符串函数strcpy不同,它不会因为遇到\0而停止。
1. 函数核心要点(<string.h>)
- 函数原型:
void* memcpy(void* destination, const void* source, size_t num);destination:目标内存起始地址source:源内存起始地址num:需要拷贝的字节数
- 核心特性:
✅ 不关心内存中存储的数据类型,逐字节拷贝
✅ 遇到\0不会停止,严格按照num指定的字节数拷贝
❌ 仅适用于不重叠的两块内存区域
2. 实战示例:拷贝整型数组
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = {1,2,3,4,5}; // 占 5*4=20 字节
int arr2[10] = {0};
// 拷贝arr1的20个字节到arr2
memcpy(arr2, arr1, 20);
// 打印arr2:1 2 3 4 5 0 0 0 0 0
for(int i=0; i<10; i++) printf("%d ", arr2[i]);
return 0;
}
3. 模拟实现:逐字节拷贝
memcpy的模拟实现核心是强转为char*类型,因为char*每次操作正好是1个字节,完美匹配内存函数的设计逻辑。
#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src); // 断言指针非空
void* ret = dest; // 保存目标起始地址,用于返回
// 逐字节拷贝
while (num--)
{
*(char*)dest = *(char*)src;
src = (char*)src + 1;
dest = (char*)dest + 1;
}
return ret;
}
// 测试代码
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {0};
my_memcpy(arr2, arr1, 20); // 拷贝前5个整型元素
for(int i=0; i<10; i++) printf("%d ", arr2[i]);
return 0;
}
4. 注意事项:重叠内存的坑 ❗
如果用my_memcpy处理重叠内存,会出现数据覆盖问题:
// 错误案例:拷贝重叠区域
int arr[] = {1,2,3,4,5,6,7,8,9,10};
// 想把 {3,4,5,6,7} 拷贝到 {1,2,3,4,5} 的位置
my_memcpy(arr, arr+2, 20);
// 实际输出:{3,4,3,4,3,6,7,8,9,10} → 数据被覆盖
原因是my_memcpy是从前往后拷贝,先拷贝的内容会覆盖后面待拷贝的数据。
💡 补充:VS2022等编译器对
memcpy做了优化,能处理重叠内存,但标准C规定memcpy只负责不重叠内存,跨平台开发时要严格遵守标准,重叠内存请用memmove。
二、memmove:处理重叠内存的升级版 🔄
memmove的全称是 memory move,它是memcpy的增强版,支持重叠内存区域的拷贝,是实际开发中更安全的选择。
1. 函数核心要点(<string.h>)
- 函数原型:
参数含义和void* memmove(void* destination, const void* source, size_t num);memcpy完全一致。 - 核心优势:
✅ 兼容重叠内存和不重叠内存的拷贝
✅ 底层通过判断拷贝方向(从前往后/从后往前)避免数据覆盖
2. 核心思路:分情况选择拷贝方向
我们以数组 {1,2,3,4,5,6,7,8,9,10} 为例,分析dest和src的位置关系,确定拷贝方向:
| 内存位置关系 | 拷贝方向 | 原因 |
|---|---|---|
dest <= src(目标在源左边) |
从前往后 | 先拷贝的内容不会覆盖待拷贝数据 |
dest > src(目标在源右边) |
从后往前 | 避免先拷贝的内容覆盖后面的数据 |
3. 模拟实现:双向拷贝逻辑
#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest <= src) // 情况1:目标在源左边 → 从前往后拷贝
{
while (num--)
{
*(char*)dest = *(char*)src;
src = (char*)src + 1;
dest = (char*)dest + 1;
}
}
else // 情况2:目标在源右边 → 从后往前拷贝
{
while (num--)
{
// 从最后一个字节开始拷贝
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
// 测试重叠内存拷贝
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
// 把 {3,4,5,6,7} 拷贝到 {1,2,3,4,5} 的位置
my_memmove(arr, arr+2, 20);
// 正确输出:3 4 5 6 7 6 7 8 9 10
for(int i=0; i<10; i++) printf("%d ", arr[i]);
return 0;
}
三、memset:按字节设置内存值 🎨
memset的全称是 memory set,作用是以字节为单位,将指定内存区域的每个字节都设置为目标值。它是初始化内存的常用工具,但极易因使用不当踩坑。
1. 函数核心要点(<string.h>)
- 函数原型:
void* memset(void* ptr, int value, size_t num);ptr:要设置的内存起始地址value:要设置的字节值(虽然参数是int,但实际只取低8位)num:要设置的字节数
- 核心特性:
✅ 逐字节设置值,不是逐元素
✅ 常用于初始化字符数组,或把内存置为0
2. 正确用法:设置字符数组
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "hello world";
// 从第3个字符开始,连续设置5个字节为 'x'
memset(arr+2, 'x', 5);
printf("%s\n", arr); // 输出:hexxxxxld
return 0;
}
3. 经典踩坑:设置非字符类型数组 ❌
很多人会用memset初始化整型数组,结果和预期不符:
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
// 想把数组每个元素设为1 → 实际是每个字节设为1
memset(arr, 1, 40); // 数组占 10*4=40 字节
return 0;
}
内存监视结果:每个整型元素的4个字节都被设为0x01,因此每个元素的值是 0x01010101(十进制 16843009),完全不是预期的1!
💡 结论:
memset仅适合设置单字节数据(如char),或把内存置为0(value=0时,任何类型元素都是0)。
四、memcmp:按字节比较内存区域 🆚
memcmp的全称是 memory compare,作用是以字节为单位,比较两块内存区域的前num个字节。它和strcmp的区别是:不依赖\0,可比较任意类型数据。
1. 函数核心要点(<string.h>)
- 函数原型:
int memcmp(const void* ptr1, const void* ptr2, size_t num);ptr1/ptr2:要比较的两块内存起始地址num:要比较的字节数
- 返回值规则:
▶ 若ptr1 > ptr2→ 返回 大于0 的值
▶ 若ptr1 == ptr2→ 返回 0
▶ 若ptr1 < ptr2→ 返回 小于0 的值
2. 实战示例:比较整型数组
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = {1,2,3,4,5}; // 内存布局:01 00 00 00 02 00 00 00 ...
int arr2[] = {1,2,3,4,0x12333301}; // 第5个元素内存:01 33 33 12
// 比较前17个字节(前4个元素+第5个元素的第1个字节)
int r = memcmp(arr1, arr2, 17);
printf("%d\n", r); // 输出:正数(arr1第17字节是05,arr2是01)
return 0;
}
写在最后 📝
内存操作函数是C语言直接操控内存的核心工具,和字符串函数相比,它们的通用性更强,适用范围更广,但也更容易因忽视字节操作的特性而踩坑。
掌握这些函数的关键在于三点:
- 区分
memcpy和memmove的使用场景(重叠/不重叠内存); - 牢记
memset是逐字节设置,而非逐元素; - 理解
memcmp的比较规则是基于字节的ASCII码值。
这些函数的模拟实现逻辑,也是笔面试中考察指针和内存操作的高频考点,建议大家手动敲一遍代码,加深对底层原理的理解。至此,C语言字符串和内存操作的核心内容就全部讲解完毕啦!
更多推荐


所有评论(0)