📖 目录
- 📚 1. 引言
- 📦 2. memcpy 函数:内存拷贝的基础
- 2.1 函数原型
- 2.2 功能说明
- 2.3 使用示例
- 2.4 模拟实现
- 🔄 3. memmove 函数:处理重叠内存的拷贝
- 3.1 函数原型
- 3.2 功能说明
- 3.3 使用示例
- 3.4 模拟实现
- 🧹 4. memset 函数:内存设置
- 4.1 函数原型
- 4.2 功能说明
- 4.3 使用示例
- ⚖️ 5. memcmp 函数:内存比较
- 5.1 函数原型
- 5.2 功能说明
- 5.3 使用示例
- 📝 6. 总结
- 🎯 7. 经典面试题
📚 1. 引言
大家好,欢迎来到C语言内存函数的专题讲解。在C语言编程中,内存操作是极其重要的一环,尤其是在嵌入式开发、系统编程和面试中,memcpy、memmove、memset和memcmp这四个内存函数几乎是必考知识点。
本文将带你从函数原型、使用示例、模拟实现到面试常见问题,全方位掌握这些内存函数。文章内容基于经典教材和实际开发经验,代码完整可运行,非常适合面试前的系统复习。
📦 2. memcpy 函数:内存拷贝的基础
2.1 函数原型
void*memcpy(void*destination,constvoid*source,size_tnum);2.2 功能说明
memcpy从source位置开始,向后复制num个字节的数据到destination指向的内存位置。- 这个函数在遇到
'\0'的时候并不会停下来,它只关心num指定的字节数。 - ⚠️重要限制:如果
source和destination有任何的重叠,复制的结果都是未定义的。对于重叠的内存,应该交给memmove来处理。
2.3 使用示例
#include<stdio.h>#include<string.h>intmain(){intarr1[]={1,2,3,4,5,6,7,8,9,10};intarr2[10]={0};// 将 arr1 的前 20 个字节(5个int)复制到 arr2memcpy(arr2,arr1,20);inti=0;for(i=0;i<10;i++){printf("%d ",arr2[i]);}// 输出结果:1 2 3 4 5 0 0 0 0 0return0;}2.4 模拟实现
void*memcpy(void*dst,constvoid*src,size_tcount){void*ret=dst;assert(dst);assert(src);/* * copy from lower addresses to higher addresses */while(count--){*(char*)dst=*(char*)src;dst=(char*)dst+1;src=(char*)src+1;}return(ret);}💡实现要点:
- 使用
char *指针逐字节拷贝,因为char类型占1个字节,可以精确控制拷贝的字节数。- 先保存
dst的原始地址作为返回值,方便链式调用。- 使用
assert进行空指针检查,增强代码健壮性。
🔄 3. memmove 函数:处理重叠内存的拷贝
3.1 函数原型
void*memmove(void*destination,constvoid*source,size_tnum);3.2 功能说明
- 和
memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 - 如果源空间和目标空间出现重叠,就得使用
memmove函数处理。
3.3 使用示例
#include<stdio.h>#include<string.h>intmain(){intarr1[]={1,2,3,4,5,6,7,8,9,10};// 将 arr1 的前 20 个字节复制到 arr1+2 的位置(重叠场景)memmove(arr1+2,arr1,20);inti=0;for(i=0;i<10;i++){printf("%d ",arr1[i]);}// 输出结果:1 2 1 2 3 4 5 8 9 10return0;}输出结果:
1 2 1 2 3 4 5 8 9 10
3.4 模拟实现
void*memmove(void*dst,constvoid*src,size_tcount){void*ret=dst;if(dst<=src||(char*)dst>=((char*)src+count)){/* * Non-Overlapping Buffers * copy from lower addresses to higher addresses */while(count--){*(char*)dst=*(char*)src;dst=(char*)dst+1;src=(char*)src+1;}}else{/* * Overlapping Buffers * copy from higher addresses to lower addresses */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);}💡实现要点:
- 核心思想是判断内存是否重叠,以及从哪个方向开始拷贝。
- 当
dst <= src或dst >= src + count时,说明没有重叠或目标地址在源地址之前,可以从低地址向高地址拷贝。- 当存在重叠且目标地址在源地址之后时,必须从高地址向低地址拷贝,避免覆盖未拷贝的数据。
🧹 4. memset 函数:内存设置
4.1 函数原型
void*memset(void*ptr,intvalue,size_tnum);4.2 功能说明
memset是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。
4.3 使用示例
#include<stdio.h>#include<string.h>intmain(){charstr[]="hello world";memset(str,'x',6);printf(str);// 输出结果:xxxxxxworldreturn0;}输出结果:
xxxxxxworld
💡注意事项:
memset以字节为单位操作,对于非字符数组(如int数组),设置的值会被解释为字节值。- 例如
memset(arr, 1, 20)会将每个字节设为0x01,而不是将每个int设为1。- 常用于初始化数组为0:
memset(arr, 0, sizeof(arr))。
⚖️ 5. memcmp 函数:内存比较
5.1 函数原型
intmemcmp(constvoid*ptr1,constvoid*ptr2,size_tnum);5.2 功能说明
- 比较从
ptr1和ptr2指针指向的位置开始,向后的num个字节。 - 返回值:
- 返回值
< 0:ptr1小于ptr2 - 返回值
= 0:ptr1等于ptr2 - 返回值
> 0:ptr1大于ptr2
- 返回值
5.3 使用示例
#include<stdio.h>#include<string.h>intmain(){charbuffer1[]="DWgaOtP12df0";charbuffer2[]="DWGAOTP12DF0";intn;n=memcmp(buffer1,buffer2,sizeof(buffer1));if(n>0)printf("'%s' is greater than '%s'.\n",buffer1,buffer2);elseif(n<0)printf("'%s' is less than '%s'.\n",buffer1,buffer2);elseprintf("'%s' is the same as '%s'.\n",buffer1,buffer2);return0;}💡与
strcmp的区别:
strcmp遇到'\0'就停止比较,而memcmp比较指定的num个字节。memcmp可以比较任意内存块(包括包含'\0'的数据),而strcmp只能比较字符串。
📝 6. 总结
| 函数 | 功能 | 处理重叠内存 | 停止条件 |
|---|---|---|---|
memcpy | 内存拷贝 | ❌ 未定义 | 指定字节数 |
memmove | 内存拷贝 | ✅ 安全处理 | 指定字节数 |
memset | 内存设置 | — | 指定字节数 |
memcmp | 内存比较 | — | 指定字节数 |
核心记忆口诀:
memcpy:不重叠,直接拷memmove:重叠了,分方向memset:设内存,按字节memcmp:比内存,定字节
🎯 7. 经典面试题
面试题 1:memcpy和memmove有什么区别?什么时候用memmove?
解答:
memcpy不保证能正确处理源和目标内存重叠的情况,如果内存重叠,结果是未定义的。memmove可以安全处理重叠内存。- 当源内存块和目标内存块有重叠时,必须使用
memmove;否则两者都可以使用,但memcpy通常效率略高。
面试题 2:memcpy和strcpy有什么区别?
解答:
strcpy只能用于字符串拷贝,遇到'\0'停止;memcpy可以拷贝任意类型的内存数据。strcpy不指定拷贝长度;memcpy需要指定拷贝的字节数。memcpy可以拷贝包含'\0'的数据块,而strcpy遇到'\0'就停止。
面试题 3:memset(arr, 1, sizeof(arr))能把 int 数组的每个元素设为 1 吗?
解答:
- 不能。
memset以字节为单位设置内存,int占4个字节,每个字节都会被设为0x01,所以每个int的值实际上是0x01010101(十六进制),即16843009。 - 正确做法是使用循环或
memset(arr, 0, sizeof(arr))来清零数组。
面试题 4:请手写一个memmove的实现,并说明为什么需要分方向拷贝?
解答:
- 参考上文第3.4节的模拟实现。
- 分方向拷贝的原因:当源地址和目标地址重叠,且目标地址在源地址之后时,如果从低地址向高地址拷贝,会先覆盖源数据中尚未拷贝的部分,导致数据错误。从高地址向低地址拷贝可以避免这个问题。
面试题 5:memcmp和strcmp的返回值规则一样吗?比较结果有什么不同?
解答:
- 返回值规则相同:都返回
>0、=0、<0。 - 不同点:
strcmp遇到'\0'停止比较,而memcmp比较完指定的num个字节才停止。因此对于包含'\0'的二进制数据,只能用memcmp进行比较。
🚀面试小贴士:面试官通常会让手写
memcpy或memmove的模拟实现,重点考察对内存重叠的处理、指针操作和边界条件的把握。建议在面试前多练习几遍手写代码,做到烂熟于心。