初学者小白复盘19之——内存函数
本文介绍了memcpy和memmove两个内存操作函数的使用及模拟实现。memcpy用于非重叠内存的拷贝,可以处理任意数据类型,其核心实现是将源数据按字节逐个复制到目标位置。memmove则在memcpy基础上增加了处理内存重叠的能力,通过判断目标位置是否在源数据范围内,决定从前向后或从后向前拷贝以避免数据覆盖。文章通过具体代码示例展示了两个函数的模拟实现过程,并分析了在处理重叠内存时需要采用不同
1.memcpy使用和模拟实现
void * memcpy ( void * destination, const void * source, size_t num );
mem其实就是memory这个单词吗,虽然它有记忆的意思,但是在这里他表示为内存,cpy其实就是英文单词copy嘛,所以从这个名字上来看我们是不是就猜到了这个函数是什么作用呀!没错,就是完成内存拷贝的一个功能。
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。
- 这个函数在遇到’\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
对于我们想要查看的c语言库函数来说,我们可以到:https://cplusplus.com/去查看
那么在此之前,我们是不是学过一个函数名为strcpy,没错,这个函数是针对字符串完成的拷贝,但是我们在写代码时肯定不能只遇到关于字符串的拷贝呀,也有可能时整型数组的拷贝等等,所以就有了memcpy的诞生。
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = {0};
memcpy(arr2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}

很好,当我们学会使用memcpy这个函数时,我们现在来尝试进行模拟实现memcpy这个函数:
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
for (int i = 0; i < num; i++)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1,20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}

很好,那我们现在就来分析一下,我们刚才模拟实现的这个代码究竟是如何实现的?
因为我们要求是任意类型对吧,所以传的参数都是void*,而字节数为size_t类型,那么一开始我们是不是要开始进行拷贝啊,由于这里是void*类型的,所以是不是不能直接进行拷贝,我们因为是任意类型,所以字节数是不是要保证所有类型都可以呀,所以我们应该把dest和src强制转化为char*类型的,一个字节一个字节的去进行拷贝对吧!,那么拷贝完指针++去找下一个位置对吧,这里有两种写法,第一种就是我们上图这种写法,还有一种就是+1的形式对吧,如下:
dest=(char*)dest+1;
src=(char*)src+1;
那么根据c语言库函数的介绍,memcpy返回类型为void*这里返回的是dest首元素地址,也就是我们所拷贝的那个首地址对吧,如下为:https://cplusplus.com/介绍的返回值
当然,在这里我们进行拷贝时可以不运用for循环,我们运用while循环是不是也一样的啊,如下:
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while(num--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return ret;
}
那么最后我们害怕传过来的是空指针,我们可以使用assert断言一下,如果为空指针,则直接进行报错,这样做会更加保险一点。
那可能有些同学会说,既然如此,能不能自己拷贝自己呢,如下:
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while(num--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr1+2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
我们期望输出结果是不是这样的:1 2 1 2 3 4 5 8 9 10.
那我们来看看实际运行结果:
欸?这是怎么回事,怎么跟我预想的不一样呢?
这是因为当我们拷贝时,会把3改为1,4改为2是吧,但是接下来拷贝的时候3已经被覆盖为1了呀,所以原来为5本应被覆盖为3的值还是1,同理,原来为6本应被覆盖为4的值还是为2,这就是原因。不过这里是我们写的有问题对吧,那我们来试一试memcpy到底行不行呢?
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr1+2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}

我们可以发现,欸?确实被改了呀,没毛病呀,难道是我们的模拟实现出错了嘛?
这是什么情况呢?其实大概就是这个意思,就像考试一样,本来要求memcpy考60分就行,结果它考了100分,那行不行呢?当然行了,考100分不是更好嘛,不过当我们拷贝这种重叠部分时,不要去使用memcpy,memcpy只需考虑不重叠的部分,因为我们有专门的函数去应对它,那就是memmove。
2.memmove的使用和模拟实现
void * memmove ( void * destination, const void * source, size_t num );

我们可以看到memmove与memcpy的参数是相同的,其实他们的功能也大差不差,只不过memcpy处理不重叠的,而memmove处理重叠部分的。
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memmove(arr1 + 2, arr1, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}

我么可以看到memcpy和memmove的使用操作是相当类似的,在这里我就不再过多的讲解。
下面我们来进行memmove的模拟实现:
那么对于memmove实际上我们需要去对其进行分类讨论:
首先,我们上文已经尝试过用memcpy去自己拷贝自己,我们想要的结果是1 2 1 2 3 4 5 8 9 10,可实际却是如下
那么我们来分析下问题究竟出现在了哪里呢?
我们来画一下图片来分析,如上:
src一开始指向数组首元素,dest指向数组中第三个元素,这些都没问题,那么到底是什么问题嘞?我们看,当我们从前往后拿元素时会不会变成这个样子:
你看,从前往后拿的时候,1和2会覆盖掉原本的3和4导致我们在往后走的时候又变成1和2了对吧,这就是导致我们运行时出现如下这种情况的原因:
问题来了,那么我们究竟该怎么做呢?
从前往后拿不行,那我们是不是应该从后向前走呢?
你看,如果我们这么走,是不是就可以了呀,这样原来的值既不会被覆盖掉,代码也可以正常运行,那让我们来整体分类分析一下:
如图所示,如果dest落在第一部分代表着我们应该从前向后拷贝,落在中间部分的话就要从后向前,最后面的话当然就无所谓了,无论是从前向后还是从后向前都可以。
那么我们现在分析完了,下面就来模拟实现memmove。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
((char*)dest)++;
((char*)src)++;
}
}
else
{
while (num--)
{
*((char*)dest +num) = *((char*)src +num);
}
}
return ret;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ",arr[i]);
}
return 0;
}

我们可以看到,这次是不是就没有问题了呀!
下面我们再来说一下中间那里是怎么写的,逻辑是这样的,中间部分我们是不是要从后向前呀,那假设总共不是20个字节嘛,想要拿到最后一个元素往前拿对吧,也就是说我们只需要1+19是不是就可以了,那么当我们的while循环进来时,num是不是–呀,此时正好num等于19,所以直接强制转化为char*的dest+num就可以了。
3.memset函数的使用
void * memset ( void * ptr, int value, size_t num );

这个函数使用起来也较为简单,我们这里就简单来说一说:
int main()
{
char arr[] = "hello world";
memset(arr, 'a', 5);
printf("%s\n", arr);
return 0;
}

不过这里需要注意一点,memset是以字节为单位设置的,如下:
int main()
{
int arr[5] = {0};
memset(arr, 1, 20);
for (int i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

我们会发现,这里为整型时,按照我们的预期目标,这里应该被改为五个1才对,现在这是怎么回事?这就是我们所说的,memset是以字节为单位设置的,让我们调试观察一下:
现在大家懂了吧,这是以字节为单位设置的,所以每个字节都会被设置为1.
4.memcmp函数的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

- 比较从ptr1和ptr2指针指向的位置开始,向后的num个字节
这个函数其实我们之前用过,使用起来也比较简单,在这里就不再细说了。
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 1,1,1,1,1,1,1,1,1,1, };
int ret = memcmp(arr1, arr2,40);
printf("%d\n", ret);
return 0;
}

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)