【C语言】内存函数及其模拟实现

各位读者老爷好,继上篇字符串函数模拟实现之后,我现在来介绍一些内存函数,希望能对你有所帮助!另外,博主本身就是编程小白,如有不足,恳请斧正!

 无论是字符函数、字符串函数还是内存函数,都是C语言的库函数,只是我们根据它的使用对象自己给它们取得名字。博主前面介绍的字符函数和字符串函数的使用对象只能是字符或字符串!而接下来介绍的一些内存函数使用对象就不仅仅局限于字符或字符串,所以内存函数的存在是很有必要的!

1.memcpy函数

1.memcpy

读者老爷请看网站查询的信息:

【C语言】内存函数及其模拟实现_第1张图片

 简单来说,该函数的功能是:函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置,返回目标空间起始地址。

我们要注意的是:

1.使用memcpy函数需要包含头文件

2.这个函数在遇到 '\0' 的时候并不会停下来;

3.如果source和destination有任何的重叠,复制的结果都是未定义的。因为标准规定memcpy函数用来实现不重叠的内存拷贝,而重叠的内存拷贝需要用memmove函数;

好的呀!我们来用用这个函数:

#include
#include
int main()
{
	char string1[] = "hello wxxxx!";
	char string2[] = "orld\0";
	char* ret =(char*) memcpy(string1+7, string2, 5);
	printf("%s\n", ret);//从string1+7的位置开始打印
	printf("%s\n", string1);//从string1的位置开始打印

	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	//将arr2中前5个整数的数据拷贝到arr1中
	int* flag = (int*)memcpy(arr1, arr2, 20);
	while (*flag)//循环打印arr1中前5个整数
	{
		printf("%d ", *flag);
		flag++;
	}
	return 0;
}

看看结果,没毛病啊!

【C语言】内存函数及其模拟实现_第2张图片

 博主再讲一个题外话!标准规定memcpy函数用来实现不重叠的内存拷贝,而重叠的内存拷贝需要用memmove函数。但在博主当前的VS2022这个环境中,memcpy函数也能实现重叠内存的拷贝。但不是所有环境都能实现的,所以最好不要用memcpy函数使用时有内存重叠!咱们来看看重叠内存的拷贝:

#include
#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr+2, arr, 20);
	//执行完应为:1,2,1,2,3,4,5,8,9,10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

运行结果确实是:1,2,1,2,3,4,5,8,9,10。这里memcpy函数拷贝重叠内存成功!

2.memcpy模拟实现

咱们直接看一个模拟实现的参考代码吧:

void* my_memcpy(void* destination, const void* source, size_t num)
{
	assert(destination && source);//断言
	void* ret = destination;
	while (num--)//循环拷贝字节内容
	{
		*(char*)destination = *(char*)source;
		destination = (char*)destination + 1;
		source = (char*)source + 1;
	}
	return ret;
}

这个函数的模拟实现其实不是很难,但有几处的细节需要我们把握好即可,我们具体分析实现思路:

1.函数名我们不妨设置为my_memcpy,函数的参数和返回值可以直接照搬网站查询的资料。至于为什么要将返回值的类型、第一个参数的类型和第二个参数的类型都设置成void*呢?那是因为我们不知道my_memcpy函数将会运用于那种类型的数据,所以设置成void*类型才能接收任意类型的数据,且第二个参数我们不希望能通过source更改source指向空间的数据,所以用const修饰更佳。

2.将num个字节的内存内容从源内存用循环的方式拷贝到目标空间内存即可即可。但我们需要注意void*的指针不能直接访问。所以可以将其强转成char*类型为佳,因为char*类型的指针访问权限为1个字节,1又是所有数字的倍数,所以才能满足每种不同大小的数据拷贝。

3.循环体中,拷贝完一个字节的内容后,我们要将destination和source均指向下一个字节。但void*的指针+1是不被允许的。所以我们可以强转成char*类型后+1再赋值给相应指针。

4.需要一个void*类型的指针保存destinaton初始指向的空间以便返回。

咱们也可以用一用咱们这个my_memcpy函数,看看是否能达到效果:

#include
#include
void* my_memcpy(void* destination, const void* source, size_t num)
{
	assert(destination && source);//断言
	void* ret = destination;
	while (num--)//循环拷贝字节内容
	{
		*(char*)destination = *(char*)source;
		destination = (char*)destination + 1;
		source = (char*)source + 1;
	}
	return ret;
}
int main()
{
	char string1[] = "hello wxxxx!";
	char string2[] = "orld\0";
	char* ret = (char*)my_memcpy(string1 + 7, string2, 5);
	printf("%s\n", ret);//从string1+7的位置开始打印
	printf("%s\n", string1);//从string1的位置开始打印

	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	//将arr2中前5个整数的数据拷贝到arr1中
	int* flag = (int*)my_memcpy(arr1, arr2, 20);
	while (*flag)//循环打印arr1中前5个整数
	{
		printf("%d ", *flag);
		flag++;
	}
	return 0;
}

结果证明没毛病哈:

【C语言】内存函数及其模拟实现_第3张图片

2.mommove函数

1.mommove

咱看网站查询的信息如下:

【C语言】内存函数及其模拟实现_第4张图片

长话短说吧,该函数和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数处理。

老样子,读者老爷来看看这个函数的使用代码:

#include 
#include 
int main()
{
	char str[] = "memmove can be very useful......";
	memmove(str + 20, str + 15, 11);
	puts(str);
	return 0;
}

这个代码简单易懂,我们就不分析了,咱们看看结果是:"memmove can be very very usuful.",拷贝成功了!

2.memmove模拟实现

咱们也可以直接看看模拟实现的一种参考代码再做分析:

void* my_memmove(void* destination, const void* source, size_t num)
{
	assert(destination && source);
	void* ret = destination;
	if (destination < source)//源内存内容从前向后拷贝到目标空间中
	{
		while (num--)
		{
			*(char*)destination = *(char*)source;
			destination = (char*)destination+1;
			source = (char*)source+1;
		}
	}
	else//源内存内容从后向前拷贝到目标空间中
	{
		while (num--)
		{
			*((char*)destination + num) = *((char*)source+num);
		}
	}
	return ret;
}

分析:

其实这个函数的模拟实现大致于memcpy函数的模拟实现一样,但是这个函数可能用于重叠内存内容的拷贝,如果拷贝方式还是与my_memcpy一模一样的话这就会带来一些问题,我们画图看看问题所在:

如果有一个int arr[5]={1,2,3,4,5};想将元素1、2、3拷贝到3、4、5处,结果将为1,2,1,2,1,并非我们想要的1,2,1,2,3。我们看图更好理解:

【C语言】内存函数及其模拟实现_第5张图片

为什么会是这个结果呢?其实原因就是在于内存重叠的部分(绿色部分),因为这部分既是源头、内存部分,又是目标空间部分。如果my_memmove拷贝方式还是与my_memcpy一模一样的话,绿色部分会在没有拷贝给紫色部分前就被蓝色部分修改导致预期不一致。

那我们怎么解决这个问题呢?

【C语言】内存函数及其模拟实现_第6张图片

其实我们观察发现可以这样解决:

1.如果内存有重叠且destination

2.如果内存有重叠且destination>source的话,我们按照源内存内容从后向前拷贝到目标空间就可以避免上面问题,拷贝可以达到预期;

3.如果内存没有重叠,我们不管按照源内存内存从前向后还是从后向前都没问题;

SO:我们统一规定:如果destination>source的话,我们按照源内存内容从前向后拷贝到目标空间,其余的话我们都按照按照源内存内容从后向前拷贝到目标空间,这样就不用考虑是否内存重叠的问题了,my_mommove函数就可以达到memmove函数的效果!

my_mommove实现好了,我们用用看看效果:

#include
#include
void* my_memmove(void* destination, const void* source, size_t num)
{
	assert(destination && source);
	void* ret = destination;
	if (destination < source)//源内存内容从前向后拷贝到目标空间中
	{
		while (num--)
		{
			*(char*)destination = *(char*)source;
			destination = (char*)destination+1;
			source = (char*)source+1;
		}
	}
	else//源内存内容从后向前拷贝到目标空间中
	{
		while (num--)
		{
			*((char*)destination + num) = *((char*)source+num);
		}
	}
	return ret;
}
int main()
{
	char str[] = "memmove can be very useful......";
	my_memmove(str + 20, str + 15, 11);
	puts(str);

	int arr[5] = { 1,2,3,4,5 };
	my_memmove(arr+2, arr , 12);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果没毛病啊,且看结果:

【C语言】内存函数及其模拟实现_第7张图片

3.memset函数

咱们看看网站查询的信息如下:

【C语言】内存函数及其模拟实现_第8张图片

memset函数是一个填充内存块的函数。 将prt指向的内存块的前num个字节设置成value值。返回更改的初始地址ptr。

我们要注意的是:

1.使用memset函数需要包含头文件

2.memset函数是以字节为单位设置内存的;

 看一个例子吧:

#include 
#include 

int main()
{
	char str[] = "almost every programmer should know memset!";
	memset(str, '-', 6);
	puts(str);
	return 0;
}

这里运行结果是:"------ every programmr should know memset!"。结果显而易见,就不分析了。

Question:我们能不能将一个有10个整形元素且元素全为0的数组通过memset函数改成全1呢?

其实做不到,就是因为memset函数是以字节为单位设置内存的而不是以整形为单位。如果小端且32位机器存储int型数据1,在内存中存放为:01000000(16进制展示)。但通过memset函数达不到这个效果。

 我们来看看代码:

#include 
#include 
void Print(int* arr,int n)
{
	while (n--)//循环逆序打印
	{
		printf("%d ", *(arr + n));
	}
	printf("\n");
}

int main()
{
	int arr[10] = { 0 };
	int n = sizeof(arr) / sizeof(arr[0]);
	Print(arr,n);
	memset(arr, 1, 40);
	Print(arr, n);
	return 0;
}

运行结果如下:

使用memset函数后结果为什么是16843009呢?那是因为我们将一个int型数据的4个字节全改成了1,如果用16进制展示内存中的补码就是:01010101,这个数转换成10进制打印出来就是 16843009。

4.memcmp函数

网站查询如下:

【C语言】内存函数及其模拟实现_第9张图片

memcmp函数是比较两个内存块的函数。比较从ptr1和ptr2指针开始的num个字节,一对字节一对字节地比较(ptr1中第一个字节于ptr2中第一个字节比较,ptr1中第二个字节于ptr2中第二个字节比较,以此类推)。

返回值的含义如下:

1.返回大于0的数值,内存块中不匹配的第一个字节在ptr1中的值小于在ptr2中的值;

2..返回0,两个内存块内容相等;

3.返回小于0的数值,内存块中不匹配的第一个字节在ptr1中的值大于在ptr2中的值;

我们看个例子吧:

#include 
#include 
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8 };
	int arr2[] = { 1,2,3,4,5,9,10,11 };
	int ret = memcmp(arr1, arr2, 20);
	printf("%d\n", ret);
	ret = memcmp(arr1, arr2, 21);
	printf("%d\n", ret);
	return 0;
}

打印的是0和-1,结果简单易懂,就不分析了。

5.ending

感谢各位读者老爷阅读,下篇文章再见。如有不足,恳请斧正啊!祝你天天开心哈!

【C语言】内存函数及其模拟实现_第10张图片

你可能感兴趣的:(C语言,c语言)