C语言(内存函数)

Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,欢迎欢迎~~

💥个人主页:小羊在奋斗

💥所属专栏:C语言
本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为一些学友们展示一下我的学习过程及理解。文笔、排版拙劣,望见谅。

1、memcpy 的使用和模拟实现

2、memmove 的使用和模拟实现

3、memset 函数的使用

4、memcmp 函数的使用

1、memcpy 的使用和模拟实现

1.1 memcpy 函数的使用

memcpy 前面的 mem 指的是 memmory ,英文单词"记忆",在C语言中指的是内存。后面要介绍的 memmove、memset 和 memcmp 都是如此。

memcpy 是一个内存拷贝函数 ,其作用是将一个内存区域内指定的 count 个字节大小的内容拷贝到目标内存空间内。值得注意的是,虽然 memcpy 是一个内存函数,但其是定义在 **<string.h>**头文件内的。

上面关于 memcpy 函数的作用及其用法的描述还是很好理解的,这里再做一些说明。

(1)为了使 memcpy 函数可以实现对任意类型的内容拷贝,其参数定义为了 void *类型的指针;

(2)跟之前学过的字符串相关的函数一样,memcpy 函数拷贝的目标空间必须是可修改的,而被拷贝的内存区域可用 const 修饰;

(3)当 source 和 destination 有任何重叠的时候,复制的结果都是未定义的,也就是说memcpy 函数不负责重叠内存的拷贝。

这个函数还是比较简单的。

1.2 memcpy 函数的模拟实现

memcpy 函数和 strcpy 函数有相似的地方,所以其实现的逻辑也就应该和 strcpy 函数的模拟实现类似。首先我们需要搞清楚,为了实现对任意类型的内容拷贝,我们将 memcpy 函数的参数及其返回值都设定成了 void * 类型的指针,但是 void * 类型的指针有缺点,不能直接进行解引用,也不能对其进行 +- 操作,那我们就要想一个办法解决这个问题。

其实这个问题我们之前在模拟实现 qsort 函数的时候就有了一个解决办法,就是将其强转为char * 类型的指针,因为 char * 类型的指针指向的对象大小是一个字节,是所有类型中字节大小最小的,不管对象类型是多大字节,只要一个字节一个字节地拷贝,就可以对实现对任意类型的内容拷贝了。

那有了上面的思路,我们就能写出下面的代码:

#include <stdio.h>
#include <assert.h>

void* my_memcpy(void* dest, const void* sour, size_t count)
{
	assert(dest && sour);
	void* pd = dest;
	while (count--)//控制拷贝多少个字节
	{
		*(char*)dest = *(char*)sour;
		((char*)dest)++;
		((char*)sour)++;
	}
	return pd;//返回目标空间的起始地址
}

void text1()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = { 0 };
	int* pi = my_memcpy(arr2, arr1 + 2, 20);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(pi + i));
	}
	printf("\n");
}

void text2()
{
	char str1[] = "abcdefghijklmnopqrstuvwxyz";
	char str2[20] = { 0 };
	char* ps = my_memcpy(str2, str1 + 5, 10);
	printf("%s\n", ps);
}

int main()
{
	text1();
	text2();
	return 0;
}

跟之前我们模拟实现 qsort 函数相比,这就是张飞吃豆芽。

上面我们是将一个内存区域的内容拷贝到另一个内存区域,那能不能实现在一个内存区域内的拷贝呢?我们来试一下:

可以看到,当我们在一个内存区域内拷贝,并且内存有重叠的时候,my_memcpy 函数就不能完成我们想要的结果了,这是因为重叠的部分已经被拷贝过来的内容代替,原内容就消失了,当拷贝到重叠的内存区域时,拷贝的还是之前拷贝过来的内容。不过只要内存不重叠,在一个内存区域内拷贝也是可行的。

但是,我们可以看到 memcpy 函数并没有这个问题,那是我们模拟的函数有问题吗?其实不是的,memcpy 函数之所以没有这个问题,是因为在某些系统上,memcpy函数可能会检测是否源内存和目标内存有重叠,并采取一些措施以确保正确的结果。然而,这种行为是不可靠的,不同的编译器或系统的实现方式可能会导致不同的结果。

上面的情况和我们在****字符、字符串函数**** 中介绍到的用 strcat 函数实现一个字符串自己拼接到自己末尾产生的问题是类似的,同样的 strcat 函数表面上虽然也没有什么问题,但是这种行为也是不可靠的。为了代码的可移植性和安全性,最好还是使用memmove 函数来处理重叠内存的情况。接下来我们就来介绍 memmove 函数。

2、memmove 的使用和模拟实现

2.1 memmove函数的使用

对比 memcpy 函数,memmove 函数与之是及其相似的,特别的是 memmove 函数操作的对象是可以重叠的 ,正如它所描述的它会将内容如同先复制到一个临时数组中,这样就解决了目标内存区域的内容被覆盖的问题。

2.1 memmove 函数的模拟实现

那么了解了 memmove 函数的逻辑,模拟实现它也不是什么难事。我们只需要创建一个临时数组过渡就行,于是就得到了下面的代码:

#include <stdio.h>
#include <assert.h>

void* my_memmove(void* dest, const void* sour, size_t count)
{
	assert(dest && sour);
	void* pd = dest;
	int i = 0;
	char arr[1000] = { 0 };
	for (i = 0; i < count; i++)
	{
		arr[i] = *(char*)sour;
		((char*)sour)++;
	}
	for (i = 0; i < count; i++)
	{
		*(char*)dest = arr[i];
		((char*)dest)++;
	}
	return pd;
}

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

是不是很简单呢,这样我们就实现了模拟 memmove 函数的功能。但是上面这种创建临时字符数组的办法有一点不足,因为我们并不能确定被拷贝的内容有多大,所以只能模糊地创建一个比较大的数组,但是这个比较大是多大没办法知道,创建大了浪费,创建小了不够,那有没有什么办法能解决这个问题呢?

2.3 memmove 函数的模拟优化

既然我们并不能确定要创建一个多大的临时数组,那我们干脆放弃创建临时数组的方法另辟奇径。

让我们再回到之前遇到的问题,如果内存重叠时拷贝会将原内容覆盖。那是不是我们拷贝的方法有问题呢?来看:

将红色方框内的内容拷贝到蓝色方框内:

我们发现,从前向后 拷贝行不通,因为会覆盖掉还没拷贝的内容;但从后向前拷贝是可行的,并没有出现还没拷贝的内容被覆盖的情况。

将蓝色方框内的内容拷贝到红色方框内:

我们又发现,从后向前 行不通,但从前向后是可行的。

而之所以有时需要从前向后拷贝,有时需要从后向前拷贝,是取决于是将前面的内容拷贝到后面,还是将后面的内容拷贝到前面。

前面介绍数组的时候我们说过,数组元素随着下标的增大地址逐渐增大。也就是说,**如果上面需要将红色方框内的内容拷贝到蓝色方框内,那么当指针p1小于指针p2时,需要从后向前拷贝;当指针p1大于指针p2时,需要从前向后拷贝。**而当两个内存区域没有重叠时,从前向后和从后向前都是可行的。

那么,我们就可以在拷贝之前先比较一下指针dest和指针sour的大小,然后再选择是从前向后拷贝还是从后向前拷贝。

#include <stdio.h>
#include <assert.h>

void* my_memmove(void* dest, const void* sour, size_t count)
{
	void* pd = dest;
	assert(dest && sour);
	if (dest < sour)//从前向后
	{
		while (count--)
		{
			*(char*)dest = *(char*)sour;
			((char*)dest)++;
			((char*)sour)++;
		}
	}
	else//从后向前
	{
		while (count--)
		{
			*((char*)dest + count) = *((char*)sour + count);
		}
	}
	return pd;
}

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

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

void text3()
{
	int i = 0;
	char str1[] = "abcdefghijklmn";
	int sz = sizeof(str1) / sizeof(str1[0]);
	my_memmove(str1, str1 + 2, 5);
	printf("%s\n", str1);
}

void text4()
{
	int i = 0;
	char str1[] = "abcdefghijklmn";
	int sz = sizeof(str1) / sizeof(str1[0]);
	my_memmove(str1 + 2, str1, 5);
	printf("%s\n", str1);
}

int main()
{
	//text1();
	//text2();
	//text3():
	text4();
	return 0;
}

这时候我们写的 my_memmove 函数就比较完善了。

其实小伙伴们也能感觉到 memmove 函数完全可以代替 memcpy 函数,而且 memmove 函数不用管内存是否重叠的问题。那 memcpy 函数不就没有存在的必要了吗?其实内存重叠只是一种特殊情况,在确定没有内存重叠的情况下使用 memcpy 函数效率会更高,因为 memcpy 函数没有比较指针大小这一步骤。

当然如果你嫌麻烦始终使用 memmove 函数也是没有什么问题的,就是效率低那么一丢丢而已。

3、**memset**函数的使用

memset 函数是用来设置内存的,它的作用是将内存中的值以字节为单位设置成想要的内容。

需要注意的是,memset 函数是以字节为单位设置的,否则会写成下面这种代码:

我们知道整型占4个字节,整数7以16进制表示为:0x07 00 00 00,上面的代码执行过后就变成了:0x01 01 01 01,并没有达到我们想要的效果。所以我们要谨记 memset 函数是以字节为单位一个字节一个字节设置的,并不是以元素为单位的。

4、memcmp 函数的使用

memcmp 函数和 strncmp 函数极其相似,也是比较两个指针指向内容的大小,唯一的区别是 strncmp 只能比较字符串,而 memcmp 可以比较任意类型。和 memset 函数一样 memcmp 也是以字节为单位比较的。

以上所有的函数都是可以操作内存的函数,与前面介绍的字符、字符串函数不同的是内存函数可以操作任意类型的内容。

如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。

相关推荐
Lizhihao_22 分钟前
JAVA-队列
java·开发语言
远望清一色40 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧1 小时前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices1 小时前
C++如何调用Python脚本
开发语言·c++·python
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
wyh要好好学习1 小时前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
AitTech1 小时前
C#实现:电脑系统信息的全面获取与监控
开发语言·c#
qing_0406031 小时前
C++——多态
开发语言·c++·多态
孙同学_1 小时前
【C++】—掌握STL vector 类:“Vector简介:动态数组的高效应用”
开发语言·c++
froginwe111 小时前
XML 编辑器:功能、选择与使用技巧
开发语言