C语言学习笔记20260621-内存库函数模拟实现(memcpy与memmove)

C语言学习笔记20260621-内存库函数模拟实现(memcpy与memmove)

一、学习目标

深入理解C语言底层内存操作的核心机制,掌握memcpymemmove的底层实现原理。重点剖析void*泛型指针的字节级操作技巧,以及memmove在处理内存重叠时的双向拷贝逻辑,从而写出安全、健壮的底层代码。

二、模拟实现 memcpy(非重叠内存拷贝)

2.1 核心思路

memcpy的作用是从源内存地址向目标内存地址拷贝指定字节数的数据。由于内存中存储的数据类型是未知的,因此必须将指针强制转换为char*,以"字节"为最小单位进行逐字节搬运。

2.2 代码解析与规范

c 复制代码
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;
	}
	return(ret);
}

2.3 关键细节与致命隐患

  • void*的强制转换void*不能直接解引用或进行加减运算。将其强转为char*是因为char在C语言中正好占用1个字节,配合count参数可以实现对任意类型数据的精准拷贝。
  • 保存起始地址 :由于dst指针在循环中不断向后偏移,最终会指向内存块的末尾。因此必须在开头用ret保存初始地址,以便支持链式调用。
  • 致命隐患(内存重叠) :标准memcpy严禁用于处理内存重叠的场景。如果目标区域和源区域有重叠,且目标地址位于源地址的高位,从前向后的拷贝会直接覆盖掉源内存中尚未拷贝的数据,导致数据错乱。

三、模拟实现 memmove(安全内存移动)

3.1 核心思路

memmovememcpy的安全升级版,专门用于处理源内存与目标内存存在重叠的情况。其核心逻辑是:通过判断源地址和目标地址的相对位置,动态决定是"从前向后"拷贝,还是"从后向前"拷贝。

3.2 代码深度解析

c 复制代码
void* memmove(void* dst, const void* src, size_t count)
{
	void* ret = dst;
	// 情况1:无重叠,或目标在源的前方(安全,可正向拷贝)
	if (dst <= src || (char*)dst >= ((char*)src + count)) {
		while (count--) {
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;
			src = (char*)src + 1;
		}
	}
	// 情况2:发生重叠且目标在源的后方(必须反向拷贝)
	else {
		// 将指针先移动到内存块的末尾
		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);
}

3.3 内存重叠的决策逻辑

  • 正向拷贝(低地址 -> 高地址) :当dst <= src(目标在源前面)或者两者完全不重叠时,源内存的数据在拷贝过程中不会被破坏,直接采用与memcpy相同的正向拷贝即可。
  • 反向拷贝(高地址 -> 低地址) :当dst > src且两者存在重叠时,源内存的后半部分与目标内存的前半部分重叠。此时如果正向拷贝,源数据会被立刻覆盖。因此,必须先将指针偏移到末尾,采用"倒着拷贝"的方式,确保在覆盖源数据之前,该数据已经被安全搬运到了目标位置。

四、总结与工程实践建议

memcpymemmove是C语言操作内存的基石。在底层开发中,void*char*的字节级操作是处理泛型数据的通用范式。在实际工程应用中,如果无法百分之百确定两块内存区域是否重叠,应无条件优先使用 **memmove**,以牺牲极微小的性能为代价,换取程序的绝对安全与稳定。此外,在编写此类底层函数时,务必使用assert对传入的指针进行非空校验,这是防御性编程的重要习惯。