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;
}

在这里插入图片描述

Logo

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

更多推荐