memcpy、memmove,memcmp——内存函数三剑客

目录

0.前言

1.memcpy函数

 2.memmove函数

 3.memcmp函数


0.前言

首先我们要知道memory在计算机科学(Computer Science即CS)即当中是内存的意思,由可得知三剑客函数主要就从内存开始操作入手,内存函数通过访问地址的方式操作对象,可以应用在任何类型的对象上面

那么当我们知道内存函数以及三剑客的名字之后我们首先就要另外知道一个概念即void(无类型)

void字面的意思就是无类型,那么无类型的指针就应当是void*,void*可以指向任何类型的数据地址,void*无需强制转换就可以接收任何类型的地址,十分有“包容性”

void*可以包容很多,但是,不代表其他数据类型能够包容void*,例如

#include<stdio.h>
int main()
{
    int* a;
    void* b;
    a = b;
    return 0;
}

这段代码就是错误代码,如果想要程序能够正常执行那么只需要将a与b的位置调换即可

b = a;

1.memcpy函数

memcpy函数与strcpy很像但是又不像,strcpy函数与memcpy函数的区别就在于strcpy函数只能作用于字符串上,而memcpy函数能够作用于任何类型的数据包括(浮点型,结构体等等)

memcpy函数能够将源头数据转移到目的地的内容里,那么memcpy该如何使用呢?

使用memcpy函数前程序应该带上<stdlib.h>的头文件,memcpy( , , )第一个位置前放你的目的地数据即你想要放进去数据的地址,第二个位置放你拿取数据源头的地址,第三个位置输入你想要读取数据的数字大小,单位是字节(Byte),例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int ch[10] = { 0 };
	memcpy(ch, arr, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ch[i]);
	}
	return 0;
}

 那么我们知道,memcpy函数是将内存里读取到的内容拷贝过去,那么不妨我们自己实现一个memcpy函数,了解了这个函数的工作原理

首先呢,我们要将源头和目的地的地址传进memcpy函数当中,这个函数就会接收到这两个数据的地址,但是我们不知道要传的是什么类型的数据地址,所以保险起见,我们就要用到我们万金油的数据类型void*来接收地址啦

其次我们知道,我们要拷贝的数据内容改变的只有一个目的地的内容,所以源头的地址内容应该是固定不变的,这个时候就要用上常量const来修饰

最后,我们还要将想要拷贝的数据大小的数字传入到memcpy这个函数当中,理所当然的这个数字的大小也应该是不能被改变的所以函数接收的时候应该将其定为常量,我们就用size_t来修饰

例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
void* my_memcpy(void* dest, void* rsc, size_t num)
{
	char* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)rsc;
		dest = (char*)dest + 1;
		rsc = (char*)rsc + 1;
	}
	return ret;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int ch[10] = { 0 };
	my_memcpy(ch, arr, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", ch[i]);
	}
	return 0;
}

那么这个例子中my_memcpy函数该怎么解读呢?

首先我们要知道,如果数据地址操作内容会涉及到变化,我们就不能将变化后的地址返回,所以我们在开头的时候首先将一开始的地址保存下来,等到我们完成了内容操作之后返回给原地址即可

其次,void*的数据类型不能被操作,所以我们选择数据类型字节最小的char*来操作,因为char*字节大小是一个字节这样我们就能一个个字节的去读取操作了,将源头rsc的数据内容拷贝到dest后让dest和rsc的地址+1,指向下一个内容去拷贝

最后,我们的num就发挥作用了,首先整形数组一个元素4个字节而我们知道,第一个元素1在内存中是以01 00 00 00的方式存储,01就是第一个字节的内容,00就是第二个字节的内容,以此类推,02 00 00 00中的02就是第五个字节的内容,让num即读取数据大小的数字不断--,直到num为0,读取了20个字节的大小,同时也拷贝了20个字节的大小内容,这个时候0为假,while的循环就被跳出去了,最后返回一开始的目的地地址,这样我们内存的操作就算完成啦

 

 2.memmove函数

memove函数与memcpy函数的功能十分相像,但是memove比memcpy更加强大,而且memmove是加强版的memcpy函数,为什么这么说呢?

其实memcpy函数在拷贝内容上有个缺点,就是自己的内容拷贝到自己身上的话会出现错误,我们将上面的代码搬过来举个例子,我想将原数组的1,2,3,4,5的内容放到3,4,5,6,7,当中,那么我们理想的结果就应该是1,2,1,2,3,4,5,8,9,10。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int ch[10] = { 0 };
	memcpy(arr+2, arr, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

但是我们的结果却变成了这样

 这就是memcpy函数自身存在的不足,面对内容重合相同或者从原数据拷贝到原数据身上时就会出现错误。我们来细细分析一下为什么会出现这样的错误呢?

很明显我们看到,当rsc读取到第三个元素时,这里本来的元素是3,后来被改成了1,此时dest在第五个元素的5的位置,这个时候就把第三位元素放进去5->1而不是3->1,因为原来的3被改了,导致了这不是我们想要的效果,那么我们看看memmove的表现如何?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr+2, arr, 20);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 

 很明显可以看到memmove的表现十分成功啊,这个函数改掉了memcpy函数的毛病,那么这让我们好奇,既然memmove的功能与memcpy的功能十分像但是又更加完善那工作原理和memcpy到底有什么区别变化呢?现在让我们讨论一下该如何优化memcpy这个函数,让他变成memmove函数

我们知道,当memcpy读取两个同样数据地址并且拷贝就会冲突,那么我们该如何避免这样的错误呢?

我们可以看到当rsc的模块在dest的模块前的时候,我们就得不到自己想要的效果,地址在内存的存储由小->大,所以此刻,rsc的地址比dest的小,所以我们设计模拟memmove函数的时候就要加上判断条件,当rsc<dest的时候该怎么走,既然从前面开始的时候会出现错误,那么 不妨让我们从模块的尾部开始,即7->5,6->4这样从后面开始,我们会发现这样就不会出现冲突,得到的就是我们想要的结果了。

那么当dest>rsc的时候呢?我们会发现3->1   4->2    5->3    6->4这样的结果,似乎完全不受顺序影响!也就是说当dest在前面的时候,内核实现的逻辑其实是和memcpy函数的内部逻辑是一模一样的!这样我们立刻就清楚的知道了memmove的函数该如何设计了

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
void* my_memmove(void* dest,const void* rsc, size_t num)
{
	char* ret = dest;
		if (dest < rsc)
		{
			while (num--)
			{
				*(char*)dest = *(char*)rsc;
				dest = (char*)dest + 1;
				rsc = (char*)rsc + 1;
			}
			return ret;
		}
		else
		{
			while (num--)
			{
				*((char*)dest + num) = *((char*)rsc + 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;
}

我们发现memmove函数的内核在memcpy基础上多了一部分的内容,就是新加的判断内容

当dest>rsc即rsc在前面,dest在后面的时候,我们强制转化为char*类型找到当前指针指向的元素地址,在元素地址上+num也就是你想要拷贝的数据大小,这样实现从后面开始拷贝,随着num--往前拷贝的效果了!当num为0跳出循环,这样就能实现我们想要的效果了!

 3.memcmp函数

memcmp函数是在strcmp的基础上面向所有数据类型比较的内存函数,格式与上面两个内存函数相同,memcmp(被比较内容,比较内容,要比较的字节大小)

int memcmp(const void* dest,const void* rsc,size_t count)

 如果dest>rsc,那么返回一个正数,dest = rsc返回0,dest<rsc返回负数

现在就让我们模拟实现一下

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
 
int my_memcmp(const void *dest, const void *rsc, size_t num)
{
	assert(dest && rsc);
 
	while (num--)
	{
		if (*(char*)dest != *(char*)rsc)
		{
			return *(char*)dest - *(char*)rsc;
		}
		dest = (char*)dest + 1;         
		rsc = (char*)rsc + 1;
	}
	return 0;
}
 
int main()
{
 
	int arr1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int arr2[10] = { 1, 2, 3, 4, 5};
	printf("%d\n", my_memcmp(arr1, arr2, 12));
    return 0;
}

memcmp函数本质是上通过每个字节里内容的ascll码值进行大小的比对,如果遇上'\0',那么ascll码值就是0,除了\0,其他都会得出除了相等以外的结果,这个时候就能比较大小了

希望我的博客对你能够有所帮助,早日成为大牛~~~


      

京子小可爱压轴~~~~~~~

 

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值