C语言内存函数 —— 不止是字符串

发布时间:2023-01-11 15:30

1. memcpy —— 内存拷贝

1.1 memcpy 的声明与用处

我们学习了 strcpy 这个函数,那么对于 memcpy 这个函数是很好理解的。mem 表 memory ,即内存,cpy 表 copy ,即拷贝。这个函数的优势在于可以拷贝任意类型的数据,而不是仅仅局限于字符串。那么我们通过 cplusplus 这个网站来观察这个函数的声明以及各个参数的意义。

C语言内存函数 —— 不止是字符串_第1张图片

我们翻译一下即可知道 memcpy 作用,即可以把 source 指针指向的内容拷贝复制至 destination 指针指向的空间。这个函数是不限数据类型的,并且可以精确拷贝 num 个字节的数据。

那么仔细观察这个函数的参数,与 strncpy 是比较类似的。这里也提到一句,即 memcpy 是不应该处理空间重叠的数据拷贝

1.2 memcpy 的用法

#include 
#include 

int main()
{
	int dest[5] = { 0 };
	int src[5] = { 1,2,3,4,5 };
	memcpy(dest, src, 20);//将 src 中的 20 个字节的数据拷贝至 dest 数组中
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", dest[i]);
	}
	return 0;
}

C语言内存函数 —— 不止是字符串_第2张图片

上面是典型的正确用法,那么刚才提到的 memcpy 不应该处理空间重叠的内容拷贝是什么意思呢?

#include 
#include 

int main()
{
	int dest[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(dest + 3, dest, 20);
	return 0;
}

这种做法是不安全的,在某些编译器运行时,会产生令人失望的效果。但在 Visual Studio 2022 或者其他版本的编译器中,memcpy 是能够处理空间重叠的拷贝的。如果在其他的编译器、其他的配置环境中,有可能是处理不了的,即产生令人失望的效果。这种效果是什么呢?就好比像我们的这段代码是想要将 1,2,3,4,5 拷贝至 4,5,6,7,8 的内存空间中,但是真正要拷贝的时候,1 会放到 4 的位置上,2 会放到 5 的位置上,3 会放到 6 的位置上,这时候 4 的位置上放的是 1 了,5 的位置上放的是 2 了,这就导致拿不到以前的 4,5 了。但是 VS 系列编译器是不会发生这种情况的。

1.3 memcpy 的模拟实现

#include 
#include 

void* my_memcpy(void* dest, const void* src, unsigned int num)
{
	assert(dest && src);//确保是有效指针
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;//既然是一个字节一个字节拷贝,那么就将指针类型转换为 char* 类型

		//强制类型转换只是一个临时变量,所以不适合自增或自减操作
		dest = (char*)dest+1;
		src = (char*)src + 1;
	}
	return ret;
}
int main()
{
	int dest[5] = { 0 };
	int src[5] = { 1,2,3,4,5 };
	my_memcpy(dest, src, 20);
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", dest[i]);
	}
	return 0;
}

C语言内存函数 —— 不止是字符串_第3张图片

2. memcmp —— 内存比较

2.1 memcmp 的声明与用处

memcmp 与 strncpy 十分相似,都是比较每一对字节的数据,其工作原理也是一样。只不过对于 memcmp 来说,能操作的数据类型更加广泛。我们通过 cplusplus 这个网站来观察 memcmp 的声明以及各个参数的意义。

C语言内存函数 —— 不止是字符串_第4张图片

这个函数的参数意义以及用法与 strncmp 并没有本质上的差别,所以我们在这里就不过多介绍了。 

2.2 memcmp 的用法

#include 
#include 

int main()
{
	int arr1[5] = { 1,3,3,4,5 };
	int arr2[5] = { 1,2,3,4,5 };
	int ret = memcmp(arr1, arr2, 20);
	if (ret > 0)
		printf("arr1 > arr2\n");
	else if (ret < 0)
		printf("arr1 < arr2\n");
	else
		printf("arr1 == arr2\n");
	return 0;
}

C语言内存函数 —— 不止是字符串_第5张图片

我们需要注意的是,就算是进行整形数据的比较,实际上进行比较的时候还是一个字节一个字节的进行。

C语言内存函数 —— 不止是字符串_第6张图片

 C语言内存函数 —— 不止是字符串_第7张图片

但是当我们碰到这段代码,memcmp 的劣势就体现出来了。

#include 
#include 

int main()
{
	int arr1[5] = { 1,16842752,3,4,5 };
	int arr2[5] = { 1,2,3,4,5 };
	int ret = memcmp(arr1, arr2, 20);
	if (ret > 0)
		printf("arr1 > arr2\n");
	else if (ret < 0)
		printf("arr1 < arr2\n");
	else
		printf("arr1 == arr2\n");
	return 0;
}

C语言内存函数 —— 不止是字符串_第8张图片

我们掌握了上面分析的原理以及上一篇博客中 strcmp 的工作原理,我们就不难理解这一个“令人惊讶”的输出结果。

C语言内存函数 —— 不止是字符串_第9张图片

那么想要解决这个 “bug” C 语言是做不到的,因为 C 语言没有判断数据类型的函数,所以想要解决这个 “bug” 就要使用 C++ 的某些方法。

2.3 memcmp 的模拟实现

#include 
#include 

int my_memcmp(const void* p1, const void* p2, unsigned int num)//不需要改变 p1、p2 指针指向的内容,所以用 const 修饰
{
	assert(p1 && p2);//确保指针有效性
	while (num-- && *(char*)p1 == *(char*)p2)
	{
		p1 = (char*)p1 + 1;
		p2 = (char*)p2 + 1;
	}
	return *(char*)p1 - *(char*)p2;
}
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 1,2,2,4,5 };
	int ret = my_memcmp(arr1, arr2, 20);
	if (ret > 0)
		printf("arr1 > arr2\n");
	else if (ret < 0)
		printf("arr1 < arr2\n");
	else
		printf("arr1 == arr2\n");
	return 0;
}

C语言内存函数 —— 不止是字符串_第10张图片

3. memmove —— 内存拷贝

3.1 memmove 的声明与用处

memmove 的作用是与 memcpy 没区别的,都是内存拷贝。但是 memmove 的某些构造原理与 memcpy 是有一些细小的区别的。那这里就不在赘述 memmove 的声明以及各种参数的意义了,因为它与 memcpy 一样。

3.2 memmove 的用法

#include 
#include 

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

C语言内存函数 —— 不止是字符串_第11张图片

我们在这里分析一下为什么 memmove 能够实现空间重叠的拷贝。 

 C语言内存函数 —— 不止是字符串_第12张图片

 C语言内存函数 —— 不止是字符串_第13张图片

 C语言内存函数 —— 不止是字符串_第14张图片

 C语言内存函数 —— 不止是字符串_第15张图片

 C语言内存函数 —— 不止是字符串_第16张图片

 3.3 memmove 的模拟实现

#include 
#include 

void* my_memmove(void* dest, const void* src, unsigned int num)
{
	assert(dest && src);//确保指针有效性
	void* ret = dest;
	if (src < dest)//当 src 小于 dest 的地址时,从后向前拷贝
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	else//其他情况则从前向后拷贝
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	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;
}

C语言内存函数 —— 不止是字符串_第17张图片

4. memset —— 内存设置

4.1 memset 的声明与用处

mem,表 memory,即内存,set 表 设置,memset 的用处是内存设置,但更适合叫做内存初始化。我们通过 cplusplus 这个网站来观察 memset 的声明以及各个参数的意义。

C语言内存函数 —— 不止是字符串_第18张图片

我们通过翻译可以理解,memset 需要三个参数,第一个为需要初始化空间的地址,第二个参数为需要初始化为什么的值,第三个参数为字节,即要初始化多少个字节。 

4.2 memset 的用法

#include 
#include 

int main()
{
	int arr[10];
	memset(arr, 1, 40);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

C语言内存函数 —— 不止是字符串_第19张图片

不要奇怪,不要奇怪,这个函数的初始化也仅仅只能针对一个字节,整形是有 4 个字节的,每个字节都初始化为 1 ,那么打印的出来的结果很大,并不奇怪。

4.3 memset 的模拟实现

#include 
#include 

void* my_memset(void* dest, int value, unsigned int num)
{
	assert(dest);
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = value;// int 类型数据向 char 类型空间赋值会发生截断,所以语法没有错误
		dest = (char*)dest + 1;
	}
	return ret;
}
int main()
{
	int arr[10];
	my_memset(arr, 1, 40);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

C语言内存函数 —— 不止是字符串_第20张图片

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号