上文我们讲了字符串函数strcpy 这个函数的局限性太大 只能用来拷贝字符串
今天我们学习的内存函数 就可以实现任意类型的拷贝****大大提高了实用性
这有点类型于qsort函数 针对任意类型
1.memcpy
一、memcpy 核心知识点
1. 函数原型
void * memcpy ( void * destination, const void * source, size_t num );
2. 功能说明
- 从**
source** 指向的内存位置 ,向后复制num个字节 的数据,到**destination指向的内存位置**。 - 不关心内存中存放的数据类型(
void*万能指针),只按字节拷贝。 - ⚠️ 关键限制 :如果
source和destination的内存有重叠 ,结果是未定义的 (重叠场景要用memmove)。 - 这里的重叠指的是:对同一个内存空间进行拷贝 用整型数组 举例就是 数组内 拷贝到同一个数组内
- 头文件 :
#include <string.h>
3. 参数与返回值
| 参数 | 含义 |
|---|---|
destination |
目标内存地址(拷贝的数据存这里) |
source |
源内存地址(要拷贝的数据从这里来) |
num |
要拷贝的字节数 (注意:不是元素个数!) |
- 返回值:拷贝完成后,返回目标空间的起始地址(即
destination)。
二、代码演示解读
#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字节。
三、模拟实现解析
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
一、memmove 核心知识点
1. 函数原型
void * memmove ( void * destination, const void * source, size_t num );
2. 功能说明
- 和
memcpy一样,完成内存块拷贝,按字节复制数据。 - 核心区别 :它专门处理源和目标内存重叠的场景,拷贝结果是安全、可预期的。
- 头文件:
#include <string.h>
3. 参数与返回值
和 memcpy 完全一致:
| 参数 | 含义 |
|---|---|
destination |
目标内存地址(拷贝的数据存这里) |
source |
源内存地址(要拷贝的数据从这里来) |
num |
要拷贝的字节数 |
- 返回值:拷贝完成后,返回目标空间的起始地址。
二、代码演示 & 运行结果分析
#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
- 解析:
arr1[0]~arr1[4]的值1,2,3,4,5被拷贝到arr1[2]~arr1[6]的位置,数组后面的元素6,7被覆盖,变成了1,2,3,4,5,所以输出如上。
三、模拟实现解析
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);
}
核心逻辑拆解:
-
先判断是否重叠,以及重叠的方向
if条件:dst <= src(目标地址在源前面 ) 或dst >= src+count(目标地址在源的拷贝范围之后 )→ 无重叠,按memcpy的方式从前向后拷贝即可。else条件:目标地址落在源的拷贝范围内 ,且在源的后面 →从后向前倒序拷贝,避免覆盖还没复制的数据。
-
为什么要分两种拷贝方向?
- 正序拷贝:适用于无重叠场景 ,高效简单。
- 倒序拷贝:专门解决目标在源后面的重叠场景,先复制后面的数据,再复制前面的数据,保证源数据不被提前覆盖。
四、memcpy vs memmove 对比
| 特性 | memcpy |
memmove |
|---|---|---|
| 处理内存重叠 | ❌ 不支持,结果未定义 | ✅ 支持,结果安全 |
| 拷贝方向 | 固定从前向后 | 根据重叠情况 ,自动选择正序 / 倒序 |
| 适用场景 | 源和目标内存完全独立 | 源和目标可能重叠 |
| 效率 | 略高(无需判断重叠) | 略低(多了一次判断逻辑) |
3.memset
一、 memset 函数基础
1. 函数原型
void * memset ( void * ptr, int value, size_t num );
2. 核心功能
- 用于按字节批量设置内存内容 ,将指定长度的内存块填充为特定值。
- 使用时必须包含头文件:
#include <string.h>
3. 参数解析
| 参数 | 含义 | 细节 |
|---|---|---|
ptr |
内存起始地址 | 指向要设置的内存空间的起始位置 |
value |
要设置的值 | 函数会将其转换为**unsigned char** ,以字节为单位填充 |
num |
内存长度 | 单位是字节 ,控制填充的字节数 |
4. 返回值
返回 ptr(即被设置的内存块的起始地址)。
二、 代码演示解析
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf(str);
return 0;
}
运行结果
输出:xxxxxxworld
原理说明
str初始内容:h e l l o w o r l d(注意hello后有一个空格,共 6 个字符)memset(str, 'x', 6)会从**str起始地址开始** ,将前 6 个字节(hello)全部替换为'x'- 因此输出的前 6 个字符变为
xxxxxx,后面的world保持不变
三、关键注意事项
-
按字节填充的本质
memset是单字节操作,因此:- 对
char数组填充字符(如'x')是安全的; - 对
int数组填充时,会将每个字节都设为value对应的unsigned char值。例如:int arr[5]; memset(arr, 0, sizeof(arr));可以将数组清 0 ,但memset(arr, 1, sizeof(arr));不会把每个int设为 1,而是设为0x01010101(小端 / 大端下的多字节组合值)。
- 对
-
num的单位是字节计算长度时要注意:- 对数组
arr,推荐写法:memset(arr, 0, sizeof(arr));(自动计算总字节数) - 避免硬编码错误,比如
int arr[10]; memset(arr, 0, 10);只会清前 10 个字节 (前 2 个int)
- 对数组
-
value会被截断 传入的int类型value会被截断为unsigned char,因此传入0x1234实际效果和传入0x34是一样的。0x1234是一个 16 位的十六进制数 ,对应的二进制是:0001 0010 0011 0100unsigned char****只能存 8 位 ,所以memset只会取它的低 8 位 :0011 0100,也就是0x34
4.memcmp
一、 memcmp 基础信息
函数原型
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
核心功能
- 按字节比较两块内存的内容 ,从
ptr1和ptr2指向的起始位置开始,比较连续num个字节。 - 头文件:
#include <string.h>
参数说明
| 参数 | 含义 |
|---|---|
ptr1 |
指向第一块待比较内存的起始地址 |
ptr2 |
指向第二块待比较内存的起始地址 |
num |
要比较的字节数,单位是字节 |
返回值规则
| 返回值 | 含义 |
|---|---|
< 0 |
ptr1 指向的内存小于 ptr2 (第一个不同字节的 unsigned char 值,ptr1 更小) |
0 |
两块内存的前 num 个字节完全相同 |
> 0 |
ptr1 指向的内存大于 ptr2 (第一个不同字节的 unsigned char 值,ptr1 更大) |
二、 代码演示解析
#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'.
三、 关键细节与避坑
① 与 strcmp 的核心区别
| 函数 | 比较方式 | 终止条件 | 适用场景 |
|---|---|---|---|
memcmp |
按字节比较,固定长度 | 必须比较完 num 个字节,不受 '\0' 影响 |
任意类型的内存块(如 int 数组、结构体) |
strcmp |
按字符比较,字符串专用 | 遇到 '\0' 就停止 |
仅用于比较 C 风格字符串 |
⚠️ 举个反例:如果字符串中间有**'\0',strcmp 会直接截断比较** ,而**memcmp 会继续比较后续字节。**
② 比较的是**unsigned char** 值
memcmp 会把每个字节当成 unsigned char 来比较,所以有符号字符的正负不影响比较结果 ,只看无符号的数值大小。
③ num 的单位是字节
- 对数组比较时,推荐用
sizeof(数组)自动计算长度,避免硬编码错误。 - 例如:
int arr1[5], arr2[5]; memcmp(arr1, arr2, sizeof(arr1));可以安全比较两个int数组的全部字节。