C语言四大内存函数深入详解:memcpy、memmove、memcmp、memset

山间有风
山间有风
发布时间:2026-10-16 | 4人浏览过
原创

C语言提供了四个至关重要的内存操作标准库函数:memcpy、memmove、memcmp 和 memset。它们通过直接操作字节(unsigned char)来绕过类型限制,实现通用数据的复制、移动、比较和初始化。本文将逐一解析其用法、原理,并探讨在空间重叠情况下的行为差异及潜在陷阱。

1. memcpy:基础内存拷贝与非重叠风险

memcpy 是 C 语言中最常用的内存拷贝函数。与针对字符串终止符 '\0' 的 strcpy 不同,它基于明确的字节数进行数据搬运,适用于任何数据类型(如整型数组、结构体等)。

观察函数声明:

void * memcpy ( void * destination, const void * source, size_t num );

它不关心数据的具体类型,仅将源地址视为起始字节流,向后拷贝 num 个字节。

基本使用示例

以下代码演示如何将一个整型数组的前 20 个字节(即前 5 个整数)拷贝到另一个数组中:

#include <stdio.h>
#include <string.h>
int main()
{
	int dest[10] = { 0 };
	int src[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(dest, src, 20); // 拷贝 20 个字节
	return 0;
}

通过调试监视窗口可直观看到,目标数组 dest 前 20 个字节的内容已被更新为源数据:

内部逻辑模拟

理解其工作原理的最佳方式是手动模拟。由于 memcpy 以字节为单位拷贝,我们可以将所有指针强制转换为 char* 类型进行迭代:

void* AnalogMemcpy(void* dest, const void* src, unsigned int num)
{
	void* start = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src; // 逐字节赋值
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return start;
}

致命问题:内存重叠

当源地址和目标地址存在重叠时,简单的逐字节拷贝会导致严重的覆盖错误。例如,将数组 dest 的内容整体向右偏移:

int dest[10] = { 1,2,3,4,5,6,7,8,9,10 };
AnalogMemcpy(dest + 3, dest, 20); // 从第4个元素开始拷贝

预期结果应该是 {1,2,3,1,2,3,4,5,...}。但由于模拟的 AnalogMemcpy 是从低地址向高地址拷贝,后方的数据会被提前破坏:

最终运行结果为错误的乱码数据,这证明了 memcpy 并不适合处理空间重叠的场景,官方文档也通常提示此限制:

2. memmove:安全的空间重叠处理

当涉及内存重叠拷贝时,必须使用 memmove。它的参数结构与 memcpy 一致,但在逻辑上增加了安全性判断:

其核心策略是根据源地址和目标地址的相对位置决定拷贝方向:

  • 正向拷贝: 如果 src < dest(源在左,目在右),从低地址向高地址拷贝。
  • 反向拷贝: 如果 src > dest(源在右,目在左),从高地址向低地址拷贝,避免被覆盖。
void* AnalogMemmove(void* dest, const void* src, unsigned int num)
{
	void* start = dest;
	if (src < dest) 
	{
		// 反向拷贝:从末尾向前移动
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	else
	{
		// 正向拷贝:从前往后移动
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	return start;
}

值得注意的是,某些现代编译器(如 Visual Studio 2022)为了优化性能,可能将 memcpy 实现为能自动处理重叠的情况。但这并非 C 标准的规定。编写跨平台或严谨代码时,涉及重叠必须使用 memmove:

3. memcmp:内存块内容比对

类似于字符串比较函数 strcmp,memcmp 用于比较两块内存区域的大小关系。它同样以字节为单位进行比较:

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

返回值规则如下:

  • > 0:第一块内存中的字符大于第二块。
  • < 0:第一块内存中的字符小于第二块。
  • = 0:两块内存内容相等。
#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 1,2,3,3,4,5,6,7,8,9 };
	int ret = memcmp(arr1, arr2, 20); // 比较前 20 个字节
	if (ret > 0)
		printf("arr1 > arr2\n");
	else if (ret < 0)
		printf("arr1 < arr2\n");
	else
		printf("arr1 == arr2\n");
	return 0;
}

4. memset:内存初始化与常见陷阱

memset 用于将内存块的指定大小设置为某个固定值(value)。常用于清空数组或设置初始状态:

void * memset ( void * ptr, int value, size_t num );

参数解析:ptr 为起始指针,value 是要设置的字节值,num 是要设置的字节数量:

经典误区警示

memset 对每个字节进行填充。对于整型(通常是 4 字节),如果你希望将数组元素初始化为 1,直接使用 memset(arr, 1, sizeof(arr)) 是错误的!因为这将把每个字节都设为 0x01,最终构成的 32 位整数将是 0x01010101(十进制 16843009),而非 1。

下面展示一个正确的例子——将 4 个字节设为 1:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr[10] = { 0 };
	memset(arr, 1, 4); // 仅修改前 4 个字节(即第一个 int 的高低位字节)
	return 0;
}

结果如下所示:

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表