C语言笔记归纳16:内存函数
num 是字节数,不是元素个数:拷贝 5 个 int,num=5×sizeof (int)=20,不是 5;memcpy 不处理重叠内存:重叠拷贝用 memmove,避免数据覆盖;memset 按字节初始化:别用它初始化整型数组为 1、2 等数值(结果错误);memset 的 value 参数:传字符用单引号('x'),不是双引号("x",字符串地址);memcmp 按字节比较:要考虑 CPU 的
内存函数
目录
2. 🚛 memmove:内存 “智能搬家工”(处理重叠内存)
✨引言:
C 语言的字符串函数(strcpy/strcat 等)只能处理以\0结尾的字符串,但实际开发中,我们经常需要拷贝整型数组、结构体、自定义类型等 “非字符串” 的内存块 —— 这时候 “内存函数” 就成了刚需!memcpy、memmove、memset、memcmp 是针对 “任意内存块” 操作的核心函数,它们不挑数据类型,只按字节干活。今天从「使用方法 + 底层模拟实现 + 致命坑点」三个维度,用 “搬家”“刷墙” 这种通俗比喻,把这些函数扒得明明白白~
1. 🚚 memcpy:内存 “搬家工”(不处理重叠内存)
memcpy是最基础的内存拷贝函数,核心是 “按字节拷贝任意内存块”,和只认字符串的strcpy比,通用性拉满!
1.1 函数原型 + 核心特性
void* memcpy (void* destination, const void* source, size_t num);
| 参数 | 含义 |
|---|---|
| destination | 目标内存块起始地址(void*万能指针,能接 int/char/ 结构体等任意类型) |
| source | 源内存块起始地址 |
| num | 要拷贝的字节数(重点!不是元素个数) |
| 返回值 | 目标内存块起始地址(支持链式调用) |
✅ 核心特性(和 strcpy 对比):
strcpy:只拷贝字符串,遇到\0就停;memcpy:按字节拷贝,不识别\0,适配所有数据类型(int/char/ 结构体等);- ❌ 致命短板:不处理 “源和目标内存重叠” 的情况(重叠拷贝结果未定义)。
1.2 基础使用示例(拷贝整型数组)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>
int main()
{
// 需求:把arr1的前5个int(1,2,3,4,5)拷贝到arr2
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[20] = { 0 };
// 拷贝20字节(5个int × 4字节/int)
my_memcpy(arr2, arr1, 20);
// 打印结果:1 2 3 4 5 0 0 0 ...
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
1.3 模拟实现(两种写法,吃透底层)
memcpy的核心是 “按字节操作”,所以要把void*转成char*(char 是 1 字节,C 语言最小内存单位,能精准操作每一个字节)。
写法 1:指针偏移法(直观)
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest; // 保存目标起始地址(用于返回)
assert(dest && src); // 断言:防止空指针解引用
while (num--) // 每循环一次,拷贝1字节,num减1
{
// 1. 转char*:精准操作1字节
// 2. 赋值:拷贝当前字节
*(char*)dest = *(char*)src;
// 3. 指针后移:处理下一个字节
src = (char*)src + 1;
dest = (char*)dest + 1;
}
return ret; // 返回目标地址,支持链式调用
}
写法 2:自增简化版
void* my_memcpy(void* dest,const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
// 强制类型转换后自增(括号不能少!)
((char*)src)++;
((char*)dest)++;
}
return ret;
}
1.4 致命坑点:重叠内存拷贝翻车
memcpy不处理 “源和目标内存重叠” 的情况,强行用会导致数据覆盖,结果错误!
int main()
{
// 需求:把arr1的1,2,3,4,5拷贝到arr1的3,4,5,6,7位置(内存重叠)
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1+2, arr1, 20); // 拷贝20字节(5个int)
// 预期:1 2 1 2 3 4 5 8 9 10
// 实际:1 2 1 2 1 8 9 10(数据覆盖,结果错误)
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
💡 翻车原因:拷贝时先把1放到3的位置、2放到4的位置,此时原3、4已经被覆盖成1、2;后续拷贝3到5的位置时,实际拷贝的是已经被覆盖的1,最终数据全乱了!
👉 结论:重叠内存拷贝别用 memcpy,用 memmove!
2. 🚛 memmove:内存 “智能搬家工”(处理重叠内存)
memmove是memcpy的 “增强版”,核心优势是支持重叠内存的拷贝,是实际开发中更常用的内存拷贝函数。
2.1 函数原型 + 核心特性
void* memmove (void* destination, const void* source, size_t num);
- 特性:和
memcpy几乎一致,唯一区别是能安全处理重叠内存拷贝; - 返回值:目标内存块起始地址。
2.2 核心逻辑:怎么避免重叠覆盖?
关键是 “选对拷贝方向”:
| 内存位置关系 | 拷贝方向 | 原因 |
|---|---|---|
| 目标地址 < 源地址(dest < src) | 从前向后 | 源数据不会被提前覆盖,和 memcpy 一样拷贝即可 |
| 目标地址 > 源地址(dest > src) | 从后向前 | 先拷贝最后一个字节,避免源数据被提前覆盖(核心!) |
| 无重叠 | 任意方向 | 前后拷贝都不影响 |
2.3 模拟实现(智能判断拷贝方向)
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest; // 保存目标起始地址
if (dest < src) // 情况1:dest在src左边 → 从前向后拷贝
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else // 情况2:dest在src右边(重叠)或无重叠 → 从后向前拷贝
{
// 从最后一个字节开始拷贝(num初始是总字节数,num--后指向最后1字节)
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
2.4 实战:重叠内存拷贝(正确示例)
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
// 重叠拷贝:把1,2,3,4,5拷贝到3,4,5,6,7位置
my_memmove(arr + 2, arr, 5 * sizeof(int)); // 5*4=20字节
// 输出:1 2 1 2 3 4 5 8 9 10(结果正确!)
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
3. 🎨 memset:内存 “刷墙工”(按字节初始化)
memset的作用是 “把指定内存块的前 num 个字节,全部设置为同一个值”,像给墙面刷漆一样,整整齐齐!
3.1 函数原型 + 核心特性
void* memset (void* ptr, int value, size_t num);
| 参数 | 含义 |
|---|---|
| ptr | 要初始化的内存块起始地址 |
| value | 要设置的值(int 类型,但实际只取低 8 位,即 ASCII 值) |
| num | 要设置的字节数(重点!不是元素个数) |
3.2 基础使用 + 易错点
示例 1:初始化字符数组(正确用法)
int main()
{
char arr[] = "hello world";
// ❌ 易错:第二个参数传"x"(字符串地址),应该传'x'(字符)
// memset(arr, "x", 5);
// ✅ 正确:把前5个字节设为'x'
memset(arr, 'x', 5);
printf("%s\n", arr); // 输出:xxxxx world
// 把前7个字节设为'y'
memset(arr, 'y', 7);
printf("%s\n", arr); // 输出:yyyyyyyorld
return 0;
}
示例 2:初始化整型数组(致命坑点)
memset是按字节设置,不是按元素设置!新手常用来初始化整型数组为 1,结果完全不对:
int main()
{
int arr[5] = { 0 };
// 想把5个int(20字节)都设为1,实际是每个字节设为1
memset(arr, 1, 20);
// 输出:16843009 16843009 16843009 16843009 16843009
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
💡 原因解析(小端存储):int 占 4 字节,memset(arr,1,20)会把每个字节都设为0x01,单个 int 的存储形式是:01 01 01 01 → 转十进制 = 1×2⁰ + 1×2⁸ + 1×2¹⁶ + 1×2²⁴ = 16843009。
👉 结论:memset适合初始化字符数组,不适合直接初始化整型数组为非 0 值!
4. 🧮 memcmp:内存 “裁判”(按字节比较大小)
memcmp是 “按字节比较任意内存块”,和strcmp比,不挑数据类型,也不识别\0。
4.1 函数原型 + 返回规则
int memcmp(const void* ptr1, const void* ptr2, size_t num);
| 返回值 | 含义 |
|---|---|
| > 0 | ptr1 内存块 > ptr2 |
| < 0 | ptr1 内存块 < ptr2 |
| = 0 | ptr1 内存块 == ptr2 |
✅ 核心规则:逐字节比较二进制值,直到找到第一个不同的字节,或比较完 num 个字节。
4.2 实战:比较整型数组(结合大小端)
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
int arr2[] = { 1,2,3,4,8,8,8 };
// 比较前16字节(4个int × 4字节/int)
int ret1 = memcmp(arr1, arr2, 16);
printf("%d\n", ret1); // 输出:0(前4个int完全一致)
// 比较前17字节(多1字节,对应第5个int的第一个字节)
int ret2 = memcmp(arr1, arr2, 17);
printf("%d\n", ret2); // 输出:-1(arr1的第17字节更小)
return 0;
}
💡 字节分布解析(小端存储,低字节存低地址):
| 数组 | 第 1 个 int | 第 2 个 int | 第 3 个 int | 第 4 个 int | 第 5 个 int |
|---|---|---|---|---|---|
| arr1 | 01 00 00 00 | 02 00 00 00 | 03 00 00 00 | 04 00 00 00 | 05 00 00 00 |
| arr2 | 01 00 00 00 | 02 00 00 00 | 03 00 00 00 | 04 00 00 00 | 08 00 00 00 |
- 前 16 字节:完全一致 → 返回 0;
- 第 17 字节:arr1 是
05,arr2 是08→05 < 08→ 返回 - 1。
5. 🚨 核心易错点总结(避坑必看)
- num 是字节数,不是元素个数:拷贝 5 个 int,num=5×sizeof (int)=20,不是 5;
- memcpy 不处理重叠内存:重叠拷贝用 memmove,避免数据覆盖;
- memset 按字节初始化:别用它初始化整型数组为 1、2 等数值(结果错误);
- memset 的 value 参数:传字符用单引号('x'),不是双引号("x",字符串地址);
- memcmp 按字节比较:要考虑 CPU 的大小端存储(小端是主流);
- void * 指针不能直接操作:必须转 char * 后才能解引用 / 偏移(char 是 1 字节)。
🎯 最后总结
内存函数是 C 语言操作 “任意数据类型” 的核心工具,记住这几个关键点:
- 拷贝内存:无重叠用 memcpy,有重叠用 memmove(优先用 memmove,兼容性更强);
- 初始化内存:字符数组用 memset,整型数组别直接用 memset;
- 比较内存:memcmp 按字节比,要结合大小端理解结果;
- 核心思想:所有内存函数都基于 “char * 按字节操作”,这是理解底层的关键!
如果这篇内容帮你吃透了内存函数,欢迎点赞收藏🌟
更多推荐


所有评论(0)