
继前两篇博客《字符串与字符函数详解(上)》和《字符串与字符函数详解(下)》后,今天咱们来看看内存操作是函数。本文将深入解析memcpy、memmove、memset和memcmp这四大内存操作函数,通过原理分析、使用示例和模拟实现,帮助开发者掌握高效安全的内存操作技巧。特别针对内存重叠等复杂场景,揭示memmove函数的精妙设计,并澄清memset函数使用时常见的误区。
memcpy是C语言标准库中的一个函数,用于将一段内存中的内容复制到另一段内存中。头文件是<string.h>。
函数原型:
void * memcpy ( void * dest, const void * src, size_t num );解释:
memcpy的使用:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}输出想必都想到了:

那咱们来模拟实现一下memcpy函数。 memcpy的模拟实现:
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
char* s1 = (char*)dest;
const char* s2 = (const char*)src;
while (num--)
{
*s1++ = *s2++;
}
return dest;
}实现很简单,就逐字节的复制。 那万一存在重叠的内存,怎么办?例如:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
//将数组中的 1,2,3,4,5拷贝到3,4,5,6,7的位置
memcpy(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}如果成功拷贝:则数组中的元素为1,2,1,2,3,4,5,8,9,10,但是这种行为是未定义的。
在vs2022上面输出为:

如果用咱们模拟实现的memcpy函数,输出为:

这并不能代表是我们的memcpy的模拟实现是错误的,只是在vs2022中对于memcpy函数的封装完成了对重叠的内存的处理,但是C语言标准并没有规定memcpy函数实现对于重叠的内存的处理。
所以,对于重叠的内存,就交给memmove来处理。
memmove是 语言标准库中的一个函数,用于将指定数量的字节从源内存块复制到目标内存块。与memcpy不同,memmove可以安全地处理内存重叠的情况,避免数据损坏。头文件是<string.h>。
函数原型:
void * memmove ( void * dest, const void * src, size_t num );说明:
memmove的使用:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}输出:

那咱们就来模拟实现一下memmove函数。
memmove的模拟实现:
咱们就根据上面的例子先来分析法分析吧,首先,咱们要实现的无疑是:

咱们先按正常程序试一下:

那此时数组中的元素为:

再进行拷贝时:

这样会导致结果为:1,2,1,2,1,2,1,8,9,10。明显是错误的。那应该怎么拷贝呢?
咱们是从第一个空间开始拷贝的,才会导致前面拷贝的结果覆盖了后面要拷贝的值。那咱们可以从最后一个空间开始拷贝,就不会出现上面覆盖的问题了。
怎么来看一下过程:


显然,没有出现覆盖。是合理的。那怎么分析完了吗?没有。咱们上面说的,只是内存重叠的情况下源空间地址比目标空间地址小。 那咱们就来看一下内存重叠的情况下源空间地址比目标空间地址大的情况。其实,猜都可以猜出来,反向处理就行了。 代码示例:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1, arr1 + 2, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}实现的效果:

假如咱们继续用上面的方法的话,就会造成覆盖。所以,咱们这次采用其实空间开始拷贝的方法。看图解:

这样就完成了,咱们现在已经分析了内存重叠的情况。对于内存不重叠的情况,又是怎么样呢。其实,对于,内存不重叠。两种办法通用。 咱们为什么要分析这么多呢,都是因为内存重叠的问题。所以,内存不重叠怎么拷贝都行。但是,考虑到效率的问题,在允许的情况下,使用正向拷贝。故:只有真正重叠时才需要反向拷贝,其他情况优先正向拷贝。 那咱们现在来用代码实现一下: 参考代码:
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
if (num == 0 || dest == src) //无需拷贝
return dest;
assert(dest && src);
void* ret = dest;
//分两种情况
if (dest > src && (char*)dest < (char*)src+num)//内存重叠的情况下反向拷贝
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
else//正向拷贝(memcpy的功能)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
return ret;
}关于memmove函数咱们就说到这里。
memset函数是一个在C中广泛使用的标准库函数,是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。 头文件是<string.h>。
函数原型:
void *memset(void *ptr, int value, size_t num);解释:
ptr:指向要填充的内存块的指针。 value:要设置的值。 num:要设置的字节数。
memset的使用:
#include <stdio.h>
#include <string.h>
int main()
{
char ch[] = "Hello world!";
memset(ch, 'x', 5);
printf("%s", ch);
return 0;
}输出:

那咱们要改变数组ch中的llo wo为全y呢?代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
char ch[] = "Hello world!";
memset(ch + 2, 'y', 6);
printf("%s", ch);
return 0;
}输出:

那这个函数只能用来用于字符吗,不是的,也可以用来设置整形数据……但是要注意一个点memset函数是以字节为单位设置内存中的值。测试一下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr[5] = { 0 };
memset(arr, 1, 20);//是否可以将数组中的元素修改为全1;
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}答案是,不行。输出:

为什么会这样呢?
对于memset(arr, 1, 20),确实可以修改数组中5个元素,但是它是以字节为单位进行修改的。
咱们可以来看看内存中的实际变化:


所以对于数组中每个元素来说,都是01 01 01 01(16进制),十进制则为16,843,009。所以咱们用memset函数来设置整形或者其他数据类型大于1字节的数据时应该注意一下,不要使的结果在意料之外。
** memcmp 是 C 标准库中的一个函数,用于比较两块内存区域的内容。它逐字节**比较两块内存,无论存储的内容是字符串、整数还是其他类型的数据。定义在<string.h> 头文件中。
函数原型:
int memcmp(const void *str1, const void *str2, size_t n);参数说明:
str1: 指向第一块内存区域的指针。 str2: 指向第二块内存区域的指针。 n: 要比较的字节数。
返回值:
返回0: 表示两块内存的前 n 个字节完全相同。 返回负值: 表示 str1 中第一个不匹配的字节小于 str2 中对应的字节。 返回正值: 表示 str1 中第一个不匹配的字节大于 str2 中对应的字节。
memcmp的使用:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdef";
char str2[] = "abcdEF";
int ret;
// 比较两块内存的前 4 个字节
ret = memcmp(str1, str2,4);
printf("%d ", ret);
// 比较两块内存的前 5 个字节
ret = memcmp(str1, str2,5);
printf("%d ", ret);
return 0;
}使用很简单,咱们区分一个点:
与
strcmp不同,memcmp不会在遇到空字符 (\0) 时停止比较,而是严格按照指定的字节数进行比较。 适用于比较任意类型的数据,而不仅限于字符串。
那咱们可以来比较一下整形:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 1,2,3,4,4 };
int ret;
// 比较两块内存的前 4*4 个字节
ret = memcmp(arr1, arr2, 4 * sizeof(int));
printf("%d\n", ret);
// 比较两块内存的前 5*4 个字节
ret = memcmp(arr1, arr2, 5 * sizeof(int));
printf("%d\n", ret);
// 比较两块内存的前 17 个字节
ret = memcmp(arr1, arr2, 17);
printf("%d\n", ret);
return 0;
}大家对于比较前16个字节和前20个字节应该都没问题,但前17个字节是什么鬼?那咱们就来看一下内存中实际存储吧:

为什么会反向存储,建议阅读《整形在内存中的存储:补码、截断与大小端的终极指南》
对于memcmp函数,咱们会用就行。
本文系统讲解了C语言中最关键的内存操作函数,重点剖析了memcpy与memmove的区别及适用场景,通过图解和代码实现了memmove对重叠内存的安全处理。同时明确了memset函数按字节操作的特性,以及memcmp函数的内存比较机制。掌握这些内存操作函数,不仅能提升程序效率,更能避免内存操作中的常见陷阱,为开发稳定可靠的C程序奠定基础。 感谢学习至此 !!!