C语言笔记归纳16:内存函数

内存函数

目录

内存函数

目录

1. 🚚 memcpy:内存 “搬家工”(不处理重叠内存)

1.1 函数原型 + 核心特性

1.2 基础使用示例(拷贝整型数组)

1.3 模拟实现(两种写法,吃透底层)

写法 1:指针偏移法(直观)

写法 2:自增简化版

1.4 致命坑点:重叠内存拷贝翻车

2. 🚛 memmove:内存 “智能搬家工”(处理重叠内存)

2.1 函数原型 + 核心特性

2.2 核心逻辑:怎么避免重叠覆盖?

2.3 模拟实现(智能判断拷贝方向)

2.4 实战:重叠内存拷贝(正确示例)

3. 🎨 memset:内存 “刷墙工”(按字节初始化)

3.1 函数原型 + 核心特性

3.2 基础使用 + 易错点

示例 1:初始化字符数组(正确用法)

示例 2:初始化整型数组(致命坑点)

4. 🧮 memcmp:内存 “裁判”(按字节比较大小)

4.1 函数原型 + 返回规则

4.2 实战:比较整型数组(结合大小端)

5. 🚨 核心易错点总结(避坑必看)

🎯 最后总结


✨引言:

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的位置,此时原34已经被覆盖成12;后续拷贝35的位置时,实际拷贝的是已经被覆盖的1,最终数据全乱了!

👉 结论:重叠内存拷贝别用 memcpy,用 memmove!

2. 🚛 memmove:内存 “智能搬家工”(处理重叠内存)

memmovememcpy的 “增强版”,核心优势是支持重叠内存的拷贝,是实际开发中更常用的内存拷贝函数。

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);
返回值含义
> 0ptr1 内存块 > ptr2
< 0ptr1 内存块 < ptr2
= 0ptr1 内存块 == 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
arr101 00 00 0002 00 00 0003 00 00 0004 00 00 0005 00 00 00
arr201 00 00 0002 00 00 0003 00 00 0004 00 00 0008 00 00 00
  • 前 16 字节:完全一致 → 返回 0;
  • 第 17 字节:arr1 是05,arr2 是08 → 05 < 08 → 返回 - 1。

5. 🚨 核心易错点总结(避坑必看)

  1. num 是字节数,不是元素个数:拷贝 5 个 int,num=5×sizeof (int)=20,不是 5;
  2. memcpy 不处理重叠内存:重叠拷贝用 memmove,避免数据覆盖;
  3. memset 按字节初始化:别用它初始化整型数组为 1、2 等数值(结果错误);
  4. memset 的 value 参数:传字符用单引号('x'),不是双引号("x",字符串地址);
  5. memcmp 按字节比较:要考虑 CPU 的大小端存储(小端是主流);
  6. void * 指针不能直接操作:必须转 char * 后才能解引用 / 偏移(char 是 1 字节)。

🎯 最后总结

内存函数是 C 语言操作 “任意数据类型” 的核心工具,记住这几个关键点:

  • 拷贝内存:无重叠用 memcpy,有重叠用 memmove(优先用 memmove,兼容性更强);
  • 初始化内存:字符数组用 memset,整型数组别直接用 memset;
  • 比较内存:memcmp 按字节比,要结合大小端理解结果;
  • 核心思想:所有内存函数都基于 “char * 按字节操作”,这是理解底层的关键!

如果这篇内容帮你吃透了内存函数,欢迎点赞收藏🌟

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值