目录
- [1. memcpy](#1. memcpy)
-
- [1.1 代码演示](#1.1 代码演示)
- [1.2 模拟实现](#1.2 模拟实现)
- [2. memmove](#2. memmove)
-
- [2.1 代码演示](#2.1 代码演示)
- [2.2 模拟实现](#2.2 模拟实现)
- [3. memset](#3. memset)
-
- [3.1 代码演示](#3.1 代码演示)
- [3.2 总结](#3.2 总结)
- [4. memcmp](#4. memcmp)
-
- [4.1 代码演示](#4.1 代码演示)
- [4.2 总结](#4.2 总结)
1. memcpy
| 项目 | 内容 |
|---|---|
| 函数名 | memcpy |
| 头文件 | #include <string.h> |
| 函数原型 | void *memcpy(void *destination, const void *source, size_t num); |
| 功能 | 从 source 指向的内存位置开始,复制 num 个字节的数据到 destination 指向的内存位置 |
参数 destination |
目标空间地址,拷贝后的数据存放到这里 |
参数 source |
源空间地址,要被拷贝的数据从这里开始 |
参数 num |
要拷贝的数据字节数 |
| 返回值 | 返回目标空间的起始地址,即 destination |
| 注意事项 | memcpy 只负责按字节拷贝内存 ,不关心数据类型 |
| 限制条件 | 如果 source 和 destination 指向的内存区域发生重叠,结果是未定义的 |
1.1 代码演示
- 字符拷贝:
c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = {0};
char arr1[] = "abcdef";
memcpy(arr, arr1, 6);
printf("%s\n", arr1); //abcdef
return 0;
}
- 数字拷贝
c
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr2) / sizeof(arr2[0]);
memcpy(arr1, arr2, sz * 4);
for (int i = 0; i < sz; i++)
printf("%d ", arr1[i]); // 1 2 3 4 5 6 7 8 9 10
return 0;
}
画图演示:

注意:在拷贝中可能会出现重叠的情况,如下:
c
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr2) / sizeof(arr2[0]);
memcpy(arr2, arr2 + 2, 20);
//源:arr2[2] ~ arr2[6]
//目标:arr2[0] ~ arr2[4]
for (int i = 0; i < sz; i++)
printf("%d ", arr2[i]); // 3 4 5 6 7 6 7 8 9 10
return 0;
}
这是从后往前 进行拷贝的,拷贝结果和预期效果一样(但不一定是对的,是依赖编译器的实现方式和效果),但是arr2 和 arr2 + 2 属于同一个数组,源区域和目标区域发生重叠 ,使用 memcpy 会导致未定义行为 ,我们再来看从前往后拷贝 的情况:

下面列出在下面的情况下不能使用memcpy函数:
| 不能使用 memcpy 的情况 | 原因 | 正确做法 |
|---|---|---|
| 源内存和目标内存发生重叠 | memcpy 不保证重叠拷贝的正确性,会导致未定义行为 |
使用 memmove |
| 源指针或目标指针是空指针 | memcpy 会访问非法内存,程序可能崩溃 |
拷贝前判断指针是否为 NULL |
| 拷贝长度超过源内存大小 | 会读取越界内存,导致未定义行为 | 确保拷贝字节数不超过源空间大小 |
| 拷贝长度超过目标内存大小 | 会写入越界内存,可能破坏其他数据 | 确保目标空间足够大 |
| 拷贝的字节数写死为固定数字 | 不同平台上数据类型大小可能不同,容易出错 | 使用 sizeof 计算拷贝大小 |
| 目标内存是只读区域 | 向只读内存写入会导致程序崩溃 | 确保目标区域可写 |
1.2 模拟实现
c
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num) //用const来修饰src,代表源头数据中的值不能被修改
{
void* p = dest; //存储目标空间的起始地址
assert(dest && src); //断言dest和src不为空
while (num--)
{
*((char*)dest) = *((char*)src);
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return p;
}
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
//使用sizeof(arr2[0])来表示要拷贝的字节数便于代码的可移植性
my_memcpy(arr1, arr2, 3 * sizeof(arr2[0]));
for (int i = 0; i < sz; i++)
printf("%d ", arr1[i]);
return 0;
}
画图演示:

2. memmove
| 项目 | 内容 |
|---|---|
| 函数名 | memmove |
| 头文件 | #include <string.h> |
| 函数原型 | void *memmove(void *destination, const void *source, size_t num); |
| 功能 | 从 source 指向的内存位置开始,复制 num 个字节的数据到 destination 指向的内存位置 |
参数 destination |
目标空间地址,拷贝后的数据存放到这里 |
参数 source |
源空间地址,要拷贝的数据从这里开始 |
参数 num |
要拷贝的数据所占的字节数 |
| 返回值 | 返回目标空间的起始地址,即 destination |
| 重要特点 | memmove 可以处理源内存块和目标内存块重叠的情况 |
与 memcpy 的区别 |
memcpy 不适合处理重叠内存,而 memmove 可以正确处理重叠内存 |
2.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr2) / sizeof(arr2[0]);
memmove(arr1, arr2, 4 * sizeof(arr2[0]));
for (int i = 0; i < sz; i++)
printf("%d ", arr1[i]); //1 2 3 4 0 0 0 0 0 0
return 0;
}
2.2 模拟实现
c
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* p = dest;// 保存目标空间的起始地址,最后用于返回
assert(dest && src);
if (dest < src) // 如果目标地址在源地址之前,从前往后拷贝
{
//前->后
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//后->前
while (num--) // 如果目标地址在源地址之后,从后往前拷贝
{
*((char*)dest + num) = *((char*)src + num);
}
}
return p;
}
int main()
{
int arr1[10] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
my_memmove(arr2 + 3, arr2, 4 * sizeof(arr2[0]));
for (int i = 0;i < sz; i++)
printf("%d ", arr2[i]); //1 2 3 1 2 3 4 8 9 0
return 0;
}
画图演示:
从后向前的例子:(上述代码的模拟实现)

从前向后的例子:

3. memset
| 项目 | 内容 |
|---|---|
| 函数名 | memset |
| 头文件 | #include <string.h> |
| 函数原型 | void *memset(void *ptr, int value, size_t num); |
| 功能 | 从 ptr 指向的内存位置开始,将连续 num 个字节的空间设置为指定的值 |
参数 ptr |
指针,指向要设置的内存空间,也就是目标内存空间的起始地址 |
参数 value |
要设置的值,函数会将 value 转换为 unsigned char 类型后进行设置 |
参数 num |
要设置的内存长度,单位是字节 |
| 返回值 | 返回目标内存空间的起始地址,即 ptr |
| 重要特点 | memset 是按字节设置内存内容的 ,不是按数据类型逐个赋值 |
| 注意事项 | 可以安全地把内存设置为 0,但不能随意用它把 int 数组设置为 1、2 等非零整数 |
3.1 代码演示
c
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "abcdef";
memset(str, 'x', 6);
printf("%s\n", str); //xxxxxx
return 0;
}
3.2 总结
当有⼀块内存空间需要设置内容的时候,就可以使⽤memset函数,值得注意的是memset函数对内存单元的设置是以字节为单位 的。
注意,不能随意用它把 int 数组设置为非零整数,结果会有问题:

4. memcmp
| 项目 | 内容 |
|---|---|
| 函数名 | memcmp |
| 头文件 | #include <string.h> |
| 函数原型 | int memcmp(const void *ptr1, const void *ptr2, size_t num); |
| 功能 | 从 ptr1 和 ptr2 指向的内存位置开始,比较连续 num 个字节的内容 |
参数 ptr1 |
指针,指向第一块要比较的内存空间 |
参数 ptr2 |
指针,指向第二块要比较的内存空间 |
参数 num |
要比较的字节数 |
返回值 < 0 |
第一处不同字节中,ptr1 指向的字节值 小于 ptr2 指向的字节值 |
返回值 = 0 |
两块内存在前 num 个字节内完全相同 |
返回值 > 0 |
第一处不同字节中,ptr1 指向的字节值大于 ptr2 指向的字节值 |
| 重要特点 | memcmp 是按字节比较内存内容 ,不是按数据类型整体比较 |
4.1 代码演示
c
int main()
{
int arr1[] = { 1,2,3,4,5 };
// 04 00 00 00
int arr2[] = { 1,2,3,1,5 };
// 01 00 00 00
int r = memcmp(arr1, arr2, 5 * sizeof(arr1[0]));
printf("%d\n", r); //1
return 0;
}
画图演示:

4.2 总结
memcmp 是一个按字节比较两块内存内容的函数,可以指定比较长度,并通过返回值表示大小关系。memcmp函数在比较int数组的时候,memcmp是按字节进行比较,不是按int数值进行比较。

完