目录
[②: 重叠情况1](#②: 重叠情况1)
[③: 重叠情况2](#③: 重叠情况2)
[五:memcmp 对比 strcmp](#五:memcmp 对比 strcmp)
摘要:
介绍4中内存函数,memcpy,memmove,memset,memcmp。并对其中的memcpy和memmove进行模拟实现;最后对比memcmp和strcmp...
一:memcpy
1:memcpy的使用
函数原型:
void * memcpy ( void * destination, const void * source, size_t num );
参数解释:
-
dest:目标内存地址的指针,即复制后的数据存放位置。 -
src:源内存地址的指针,即要被复制的数据来源。 -
n:要复制的字节数。
函数功能:
将数据从 src 指向的内存区域复制 n 个字节到 dest 指向的内存区域。
-
按字节逐一复制。
-
不会在复制的时候遇到
\0而停止(与字符串函数strcpy不同)。 -
适用于任意数据类型(
int、float、结构体、数组等)。
返回值:
返回 dest 的指针(即目标内存区域的首地址)。
注意事项:
①:内存区域不能重叠,重叠则必须使用memmove
⚠️:对于重叠,谨记使用memmove!某些编译器上memcpy也可以处理重叠的,比如vs,但不意味着所有编译器的memcpy上都可以实现重叠拷贝!所以我们模拟实现memcpy的时候,也只需要针对不重叠的情况去实现。
②:复制'\0'不会停止,因为 memcpy 完全按字节复制,不关心内容
⚠️:所以对于字符数组,来说,如果复制的内容没有包含'\0'结束符,则需要自己手动加上
使用示例:
①:完整复制整形数组
cpp
#include <stdio.h>
#include <string.h>
int main() {
int src[] = {1, 2, 3, 4, 5};
int dest[5];
memcpy(dest, src, sizeof(src));
printf("整型数组完整复制:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", dest[i]);
}
printf("\n");
return 0;
}

**解释:**sizeof(src)计算的就是src这个被复制的数组的大小字节数
②:部分复制整形数组
cpp
#include <stdio.h>
#include <string.h>
int main() {
int src[] = { 10, 20, 30, 40, 50 };
int dest[5] = { 0 };
memcpy(dest, src, 3 * sizeof(int));
printf("整型数组部分复制(只复制前3个):\n");
for (int i = 0; i < 5; i++) {
printf("%d ", dest[i]);
}
printf("\n");
return 0;
}

**解释:**对不部分复制,往往采取sizeof计算出单个元素类型的大小,然后乘上数目
③:完整复制字符数组
cpp
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest[10];
memcpy(dest, src, strlen(src) + 1);
printf("字符数组完整复制:\n");
printf("%s\n", dest);
return 0;
}

**解释:**对于"Hello"来说strlen(src)返回值为5,但我们需要+1去把'\0'也复制
④:部分复制字符数组
cpp
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello";
char dest[10];
memcpy(dest, src, 3);
dest[3] = '\0';
printf("字符数组部分复制(只复制前3个,手动加结束符):\n");
printf("%s\n", dest);
return 0;
}

**解释:**对于字符数组的部分复制,我们往往会在dest数组后面手动置'\0'
2:memcpy的实现
void * memcpy ( void * destination, const void * source, size_t num );
两个参数的类型都是void *,是因为memcpy身为一个内存函数,是以字节为单位进行操作的,所以适配于很多类型,不知道传过来的是什么类型,所以用void *接收,内部再强转为char*以字节为单位进行操作即可
所以我们模拟实现的思路就是强转为char*之后然后进行num次的逐字节复制
代码:
cpp
#include <assert.h>
void *my_memcpy(void *dst, const void *src, size_t count)
{
void *ret = dst; // 保存原地址,用于返回
assert(dst && src); // 断言非空
while (count--) {
*(char *)dst = *(char *)src; // 逐字节复制
dst = (char *)dst + 1; // 指针后移1字节
src = (char *)src + 1;
}
return ret;
}
解释:
**①:**首先 肯定是需要用ret保存一下dst指针的,用于最后的返回,因为dst会在后面的代码中改变
**②:**断言检测dst和src指针,其一为空,则代表用于传参错误,直接报错
**③:**以count为循环次数,在while循环的内部将dst和src强转为char*进行逐字节的复制
二:memmove
1:memmove的使用
void * memmove ( void * destination, const void * source, size_t num );
参数解释:
-
dest:目标内存地址的指针,数据要复制到的位置 -
src:源内存地址的指针,数据来源的位置 -
n:要复制的字节数
函数功能:
将 src 指向的内存区域中的 n 个字节复制到 dest 指向的内存区域。
返回值:
返回 dest 的指针(目标内存区域的首地址)
注意事项
①:主要用于内存重叠场景
- 当
dest和src有重叠时,memmove是安全的选择
②:性能略低于 memcpy
-
因为
memmove需要判断是否重叠,可能牺牲少量性能 -
不重叠时优先用
memcpy,重叠时用memmove
使用示例:
**①:**不重叠
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int src[] = {1, 2, 3, 4, 5};
int dest[5];
memmove(dest, src, sizeof(src));
printf("整型数组不重叠复制:");
for (int i = 0; i < 5; i++)
printf("%d ", dest[i]);
printf("\n");
return 0;
}

**解释:**不重叠的情况下,是等同于memcpy的
②: 重叠情况1
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 10, 20, 30, 40, 50 };
// 将 [30,40,50] 移动到 [10,20,30] 的位置
memmove(arr, arr + 2, 3 * sizeof(int));
printf("整型数组向左重叠移动:");
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}


解释: 重叠情况1,就是指针src 小于 指针desr ,memmove可以应对
③: 重叠情况2
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr[] = { 10, 20, 30, 40, 50 };
// 将 [10,20,30] 移动到 [20,30,40] 的位置
memmove(arr + 2, arr, 3 * sizeof(int));
printf("整型数组向右重叠移动:");
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}


解释: 重叠情况2,就是指针src 大于 指针desr ,memmove也可以应对
2:memmove的实现
void * memmove ( void * destination, const void * source, size_t num );
对于不重叠的情况,我们不需要去讨论src和dest的大小关系,都可以直接复制
我们知道,复制的方式无非就是从前往后复制 或 从后往前复制 这两种方法
对于重叠的情况,则需要根据src和dest的大小关系分开讨论,寻找适配的复制方法!
①:情况分析
a:src 大于 dest

当src 大于 dest的时候,如果从后往前赋值,则一定出错!按照8,7,6,5,4的顺序复制给5,4,3,2,1,无法得到4,5,6,7,8,6,7,8,9,10(正确)而是得到7,8,6,7,8,6,7,8,9,10(错误)

而从前往后赋值,则正确:

b:src 小于 dest

当src 小于 dest的时候,若是从前往后复制,就是按照4,5,6,7,8的顺序复制给6,7,8,9,10,无法得到1,2,3,4,5,4,5,6,7,8(正确),而是得到1,2,3,4,5,4,5,4,5,4(错误)

而从后往前赋值,则正确:

②:规律总结
总结:
一:重叠情况,若src>dest,则从前往后进行复制;若src<dest,则从后往前复制
二:不重叠情况,则任意选择
三:统一 src>dest,前往后复制,src<dest,后往前复制
更深层次的理解:
当src(源)<dest(目的)的时候,只能后往前!

因为src后部分和dest的前部分重叠,此时采取错误的前往后拷贝,则会让src后部分还没被拷贝的元素被更新赋值了,等到之后要拷贝src后部分的元素则肯定错
当src(源)>dest(目的)的时候,只能前往后!

因为src前部分和dest的后部分重叠,此时采取错误的后往前拷贝,则会让src前部分还没被拷贝的元素被更新赋值了,等到之后要拷贝src前部分的元素则肯定错
③:实现代码
cpp
#include <assert.h>
void *my_memmove(void *dest, const void *src, size_t num)
{
void *ret = dest;
assert(dest && src);
if (src > dest)
{
// 内存不重叠或 dest 在前:从前往后复制
while (num--)
{
*(char *)dest = *(char *)src;
dest = (char *)dest + 1;
src = (char *)src + 1;
}
}
else
{
// 内存重叠且 dest 在后:从后往前复制
while (num--)
{
*((char *)dest + num) = *((char *)src + num);
}
}
return ret;
}
解释:
**①:**首先 肯定是需要用ret保存一下dst指针的,用于最后的返回,因为dst会在后面的代码中改变
**②:**断言检测dst和src指针,其一为空,则代表用于传参错误,直接报错
**③:**判断情况,若为src > dest ,则采用前往后复制,esle采取后往前复制;
**④:**前往后复制就是类似memcpy的强转为char*之后,直接num次循环逐字节复制;而后往前复制,则强转char*之后,需要让指针取指向最后一个元素的最后一个字节,然后往前逐字节复制!而while (num--)这一步,不仅循环了num次控制次数,并且让num在第一个进入循环就减1了,此时直接让强转为char*的指针+num即可指向最后一个元素的最后一个字节
三:memset
1:memset的使用
void * memset ( void * ptr, int value, size_t num );
参数解释:
-
ptr:要操作的内存区域起始地址 -
value:要设置的值(以int形式传入,但会被转换为unsigned char后使用) -
num:要设置的字节数
函数作用:
将从 ptr 开始的 num 个字节,每个字节都设置为 value 的值。
-
按字节设置,而不是按元素类型设置
-
常用于内存区域的初始化 (清零)或填充
返回值:
返回 ptr 的指针(即内存区域的首地址)
注意事项:
⚠️ 1:memset 按字节填充,不会自动保留或添加 '\0',如果覆盖了 '\0' 或忘记添加 '\0',字符串操作函数会出错
⚠️2:memset是⽤来设置内存的,将内存中的值以字节为单位设置成想要的内容,一般将整形数组置为全0,或者将字符串的某一部分置为相同的字符
使用示例:
①:清零数组
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
printf("清零前:");
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
// 将整个数组清零
memset(arr, 0, sizeof(arr));
printf("清零后:");
for (int i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}

②:设置字符串的某一部分
cpp
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Hello World";
printf("修改前:%s\n", str);
// 将第6个字符开始(World)的5个字符置为 '*'
memset(str + 6, '*', 5);
printf("修改后:%s\n", str);
return 0;
}

四:memcmp
1:memcmp的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
参数解释:
-
ptr1:指向第一块内存区域的指针 -
ptr2:指向第二块内存区域的指针 -
num:要比较的字节数
函数作用:
将 ptr1 和 ptr2 指向的内存区域的前 num 个字节逐字节进行比较。
-
按字节比较,不是按元素类型比较
-
比较的是内存中的二进制值(ASCII 码或数值)
-
遇到第一个不同的字节就停止比较
返回值:
| 返回值 | 含义 |
|---|---|
0 |
两块内存的前 num 个字节完全相同 |
> 0 |
ptr1 的第1个不同字节的值大于 ptr2 对应字节 |
< 0 |
ptr1 的第1个不同字节的值小于 ptr2 对应字节 |
注意事项:
⚠️: 按字节比较,不是按类型,其次memcmp 不会因 '\0' 停止,会完整比较 num 个字节
使用示例:
**①:**比较部分元素
cpp
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = { 1, 2, 3, 9, 9 };
// 只比较前3个元素(12字节)
int result = memcmp(arr1, arr2, 3 * sizeof(int));
if (result == 0)
printf("前3个元素相同\n");
else
printf("前3个元素不同\n");
return 0;
}

②:比较小大写
同一个字母的大小写的ascll码值不同,所以memcmp比较的时候,当然不同
cpp
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Hello";
char str2[] = "hello";
int result = memcmp(str1, str2, 5);
if (result != 0)
printf("'H'(72) vs 'h'(104),所以不相等\n");
return 0;
}

五:memcmp 对比 strcmp
对比二者,肯定是以字符数组/字符串的角度去对比的
代码:
对比 str1[] = "Hello\0World" 和 str2[] = "Hello\0ABCD"
cpp
#include <stdio.h>
#include <string.h>
int main()
{
// 两个字符串中间都含有 '\0'
char str1[] = "Hello\0World";
char str2[] = "Hello\0ABCD";
// ========== memcmp ==========
// 比较前5字节:只到 'o',还没遇到 '\0'
int r1 = memcmp(str1, str2, 5);
printf("memcmp 比较5字节:%d(相同)\n", r1);
// 比较前11字节:会越过中间的 '\0',比较后面的 'W' vs 'A'
int r2 = memcmp(str1, str2, 11);
printf("memcmp 比较11字节:%d('W' > 'A',正数)\n", r2);
// ========== strcmp ==========
// 遇到第一个 '\0' 就停止,认为前面 "Hello" 相同
int r3 = strcmp(str1, str2);
printf("strcmp:%d(遇 \\0 停止,认为相同)\n", r3);
return 0;
}

解释:
**①:**strcmp会觉得两个字符串完全相同,因为strcmp遇到'\0'就停止,所以压根比较不到后半部分
**②:**而memcmp遇到'\0'不会停止,其完全对比完之后,认为两个字符数组不相同
| 对比项 | memcmp |
strcmp |
|---|---|---|
| 头文件 | <string.h> |
<string.h> |
| 停止条件 | 比较完 num 个字节 |
遇到 '\0' 就停止 |
| 参数 | (ptr1, ptr2, num) |
(str1, str2) |
| 适用场景 | 任意内存块(二进制数据) | 字符串(文本数据) |
遇到 '\0' |
不停止,继续比较 | 立即停止 |
| 可比较的内容 | 可含 '\0' 的数据 |
不能含 '\0'(会误停) |
📌 [ 作者 ] shylyly
📃 [ 首次发布 ] 2026.5.16
❌ [ 最新修改 ] 无
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教。