《C 语言内存函数深度剖析:从原理到实战(memcpy/memmove/memset/memcmp 全解析)》

发布于:2025-08-05 ⋅ 阅读:(19) ⋅ 点赞:(0)

在C语言中,内存操作是程序设计的核心环节之一。无论是数据拷贝、内存初始化还是内存比较,都离不开专门的内存函数。本文将详细解析C语言中最常用的4个内存函数:memcpymemmovememsetmemcmp,包括它们的使用方法、模拟实现及注意事项,帮助你彻底掌握内存操作的核心技巧。

1. memcpy:内存拷贝函数

memcpy是C语言中最基础的内存拷贝函数,用于将一块内存的数据复制到另一块内存。

1.1 函数原型

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

1.2 功能说明

  • source指向的内存位置开始,向后复制num个字节的数据到destination指向的内存位置。
  • 核心特点
    • 按字节拷贝,不依赖数据类型(因此参数用void*)。
    • 遇到'\0'不会停止(区别于字符串函数strcpy)。
    • 不处理重叠内存:如果sourcedestination的内存区域有重叠,复制结果是未定义的。

1.3 示例代码及解析

#include <stdio.h>
#include <string.h>

int main()
{
    int arrx[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arry[10] = { 0 };
    // 复制arr1的前20个字节到arr2(int占4字节,即复制前5个元素)
    memcpy(arrx, arry, 20);
    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arry[i]); // 输出:1 2 3 4 5 0 0 0 0 0
    }
    return 0;
}

解析
int类型占4字节,20字节 = 5个int元素,因此memcpy(arry, arrx, 20)会将arr1的前5个元素(1-5)复制到arry,剩余元素保持初始值0。

1.4 模拟实现及解析

#include <assert.h> // 用于断言

void * memcpy ( void * dst, const void * src, size_t count)
{
    void * ret = dst; // 保存目标地址,用于返回
    assert(dst && src); // 断言确保指针非空,增强安全性
    
    // 按字节拷贝(char*每次移动1字节)
    while (count--) 
    {
        *(char *)dst = *(char *)src; // 强制转换为char*,逐个字节复制
        dst = (char *)dst + 1; // 目标指针后移1字节
        src = (char *)src + 1; // 源指针后移1字节
    }
    return ret; // 返回目标地址
}

实现要点

  • 使用char*强制转换:保证每次只操作1字节,适配任意数据类型。
  • 断言assert(dst && src):避免空指针传入导致的运行时错误。
  • 返回dst原始地址:方便链式调用(如memcpy(arr3, memcpy(arr2, arr1, 20), 20))。

1.5 注意事项

  • 禁止重叠内存:如果sourcedestination内存重叠(如memcpy(arr+2, arr, 20)),结果不可预测,此时需使用memmove
  • 拷贝长度单位:num的单位是字节,而非元素个数,需根据数据类型计算(如n个int元素n*sizeof(int)字节)。

2. memmove:处理重叠内存的拷贝函数

memmovememcpy的增强版,专门用于处理源内存和目标内存重叠的场景。

2.1 函数原型

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

2.2 功能说明

  • memcpy功能类似,均用于复制num字节数据。
  • 核心区别:支持源内存和目标内存重叠,复制结果可预测。

2.3 示例代码及解析

#include <stdio.h>
#include <string.h>

int main()
{
    int arrx[] = { 1,2,3,4,5,6,7,8,9,10 };
    // 将arr1的前20字节(1-5)复制到arr1+2位置(从索引2开始)
    memmove(arrx+2, arrx, 20); 
    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arrx[i]); // 输出:1 2 1 2 3 4 5 8 9 10
    }
    return 0;
}

解析
复制后,原索引0-4的元素(1-5)被拷贝到索引2-6的位置,因memmove处理重叠逻辑,避免了数据覆盖问题,最终结果正确。

2.4 模拟实现及解析


void * my_memmove(void * dest, void * src, size_t num)
{
	assert(dest && src);
	void * tmp = dest;
	while(num--)
	{
		if(src < dest)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
			
		}
		else
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return tmp;
}

实现核心逻辑

  • 正向拷贝:当目标内存在源内存左侧,或两者无重叠时,从起始位置依次向后复制(同memcpy)。
  • 反向拷贝:当目标内存在源内存右侧且重叠时,从末尾位置依次向前复制,避免覆盖尚未复制的源数据。

2.5 注意事项

  • 适用场景:所有内存拷贝场景(包括重叠和非重叠),建议优先使用memmove替代memcpy,避免重叠问题。
  • 性能差异:memmove因需判断重叠逻辑,性能略低于memcpy,但在现代编译器优化下差异可忽略。

3. memset:内存设置函数

memset用于将一块内存的每个字节设置为指定值,常用于内存初始化。

3.1 函数原型

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

3.2 功能说明

  • ptr指向的内存中,前num个字节逐个设置为value(低8位有效)
  • 按字节操作,无论原始数据类型如何。

3.3 示例代码及解析

#include <stdio.h>
#include <string.h>

int main ()
{
    char str[] = "hello world";
    // 将str的前6个字节设置为'x'
    memset(str, 'x', 6); 
    printf(str); // 输出:xxxxxxworld
    return 0;
}

解析
'x'的ASCII值为120,memsetstr前6个字节均设置为120,因此"hello “被替换为"xxxxxx”。

3.4 常见误区及补充

  • 按字节设置的局限性
    memset按字节操作,因此对非char类型数组设置时需注意。例如:

    int arr[5] = {0};
    memset(arr, 1, sizeof(arr)); // 错误:每个字节被设为1,而非整个int为1
    

    上述代码中,每个int(4字节)会被设置为0x01010101(十进制16843009),而非预期的1。
    正确用法:仅用于设置0(memset(arr, 0, sizeof(arr)))或 -1memset(arr, -1, sizeof(arr)),因-1的补码为全1)。

  • 适用场景:初始化字符串、清空数组、设置标记位等(如将缓冲区填充为0)。

3.5 注意事项

  • value参数:实际使用的是value的低8位(即value & 0xFF),因此传入120120+256效果相同。
  • 长度计算:num为字节数,需用sizeof获取完整内存长度(如sizeof(arr))。

4. memcmp:内存比较函数

memcmp用于比较两块内存的前num个字节,返回比较结果。

4.1 函数原型

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

4.2 功能说明

  • 比较ptr1ptr2指向的内存中,前num个字节的大小(按无符号字符比较)。
  • 返回值规则:
返回值 含义
<0 ptr1中第一个不匹配字节 < ptr2中对应字节(无符号值)
=0 num个字节完全相同
>0 ptr1中第一个不匹配字节 > ptr2中对应字节(无符号值)

4.3 示例代码及解析

#include <stdio.h>
#include <string.h>

int main()
{
    char buffer1[] = "DWgaOtP12df0";
    char buffer2[] = "DWGAOTP12DF0";
    int n = memcmp(buffer1, buffer2, sizeof(buffer1));
    
    if (n > 0) 
        printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
    else if (n < 0) 
        printf("'%s' is less than '%s'.\n", buffer1, buffer2);
    else
        printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
    // 输出:'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.
    return 0;
}

解析
比较到第3个字符时,buffer1'g'(ASCII 103),buffer2'G'(ASCII 71),因此103>71,返回值>0。

4.4 与strcmp的区别

特性 memcmp strcmp
终止条件 比较完num字节后停止 遇到'\0'停止
比较范围 精确控制比较字节数 仅比较到字符串结束
适用场景 任意内存块(包括二进制数据) 仅字符串

4.5 注意事项

  • 无符号比较:即使比较有符号数据,memcmp也会按无符号字节值比较(如-1的无符号值为255)。
  • 完整比较:需确保num不超过两块内存的实际大小,避免越界访问。

总结

C语言的内存函数是数据操作的基础工具,掌握它们的特性和差异能显著提升程序的可靠性和效率:

  • memcpy:基础内存拷贝,不支持重叠。
  • memmove:增强版拷贝,支持重叠内存,建议优先使用。
  • memset:按字节设置内存,适合初始化(注意非字符类型的局限性)。
  • memcmp:比较指定长度的内存,适用于任意数据类型的比较。

合理使用这些函数,能让内存操作更简洁、安全,避免手动字节操作带来的错误。实际开发中,需根据场景选择合适的函数,并始终注意内存边界和重叠问题。


网站公告

今日签到

点亮在社区的每一天
去签到