内存函数的模拟实现

内存函数的模拟实现,第1张

我们知道字符串函数是专门用来处理字符数组的各种 *** 作,譬如复制、拼接、计算长度等等,但是这些函数只能作用于字符串,那么如果要对其他类型的数组进行上述 *** 作时,使用字符串函数显然是行不通的。这时就只能使用一种通用类型函数,也就是内存函数来解决。

字符串函数能解决的问题,都可以用内存函数来解决。二者之间的区别是:字符串函数只能 *** 作字符串,而内存函数直接 *** 作内存。因而内存函数不需要知道被 *** 作的内容类型,只需告知内存即可。以内存拷贝函数memcpy为例,其定义如下:

void* memcpy( void *dest, const void *src, size_t count );
//dest	-	指向要复制的对象的指针
//src	-	指向复制来源对象的指针
//count	-	复制的字节数

其实现原理其实同字符串拷贝函数strcpy大同小异,要模拟实现这么一个函数,可以先类比模拟实现strcpy函数:

char* my_strcpy(char* dest, const char* src)
{
	char* p = dest;
	while (*src)
		*dest++ = *src++;
	return p;
}

如果要模拟实现memcpy函数,无非就是更改一下类型。然而设计这个函数的我们并不知道使用者传参的类型,那么我们就要接收所有的类型,void*就像一个垃圾桶,它可以接收任意类型的指针,基于这个特性,设计函数时就可以把参数类型设置成void*。当然void*只能接收不能使用,要对其指针指向的内容进行 *** 作需要将其强制类型转换成其他类型。char类型是最小的,因此可以考虑强转为char*类型,而 *** 作的内容可以通过第三个参数count来确定。

其实现如下:

void* my_memcpy(void* dest, const void* src, size_t count)
{
	void* p = dest;
	while (count--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return p;
}

这里的while循环内部有几种典型错误写法:

//第一种:
*(char*)dest = *(char*)src;
dest++;
src++;
//第二种:
*(char*)dest++ = *(char*)src++;
//第三种:
*(char*)dest = *(char*)src;
(char*)dest++;
(char*)src++;

错误原因:首先是void*类型指针不能进行任何 *** 作,+、- *** 作等都不行,只有先强制转换成其他类型才能进行指针类型的一系列 *** 作。其次是强制转换是一种临时的状态,它只会在使用时发生类型转换,而使用完后立即就会变为原来的类型,而后置++是使用完再++,那么明显++的时候指针类型就已经变为原来的void*类型,因此后置++不可用。

memcpy函数可以实现将一个数组的内容复制到另一个数组,那如果要将某个数组的部分内容拷贝到该数组的其他位置又该怎么办呢?memcpy函数可以实现吗?其实如果使用库函数memcpy函数有些编译器也是可以做到的,只不过我们模拟实现的这个函数并不能达到要求。根据C语言语法规定,memcpy函数只需要做到在不同的数组之间拷贝就达到要求了。也就是说,不能保证所有的编译器都能实现数组内部不同位置的拷贝。

memmove函数则是用来专门处理这类问题的函数,因此可以考虑使用memmove。我们也可以试着模拟实现它来更好的理解如何使用该函数。

首先是定义:

void* memmove( void* dest, const void* src, size_t count );

可以看到在定义方面是和memcpy一样的,原因不再赘述。

实现原理其实同memcpy一样:

void* my_memmove(void* dest, const void* src, size_t count)
{
	void* p = dest;
	while (count--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
	return p;
}

可是这样并不能达到要求,例如:

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(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

然而实际情况却是:

 原因在于我们是一个一个拷贝的,首先把arr[0]的数据拷贝到了arr[2],那么等到要拷贝arr[2]到arr[4]时,会发现原来的数据已经被arr[0]的数据覆盖了。要解决这个问题,其实只需要将从前往后拷变为从后往前拷就解决了。即:

void* my_memmove(void* dest, const void* src, size_t count)
{
	void* p = dest;
	while (count--)
		*((char*)dest + count) = *((char*)src + count);
	return p;
}

不过这样又会导致一个问题,如果这样拷贝:

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr1, arr1 + 2, 20);
	for (int i = 0; i < 10; i++)
		printf("%d ", arr1[i]);
	return 0;
}

实际结果为:

 其实犯了一样的错误,即在拷贝过程中,某些待拷贝的值被覆盖了,也就导致了后续的拷贝失败,那么就需要把这两种拷贝方式结合起来,即在某个条件下从前往后拷贝,其他情况从后往前拷贝。

其实只有在两个指针指向的内容重叠时才会发生内存覆盖:

第一种就必须从前往后拷贝, 第二种必须从后往前拷贝,而第三种则任意一种方式均可。为方便起见,可以把后两种合并,即dest位置在src前面时从前往后拷贝,其余情况从后往前拷贝。

即:

void* my_memmove(void* dest, const void* src, size_t count)
{
	void* p = dest;
	if (dest < src)
	{
		while (count--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (count--)
			*((char*)dest + count) = *((char*)src + count);
	}
	return p;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/2990975.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存