Bootstrap

【C语言】memcpy memmove memset memcmp 四大内存操作函数(详解+用法+模拟实现)

头文件<string.h>中常用内存操作函数共有四大,学习完本篇文章,各种类型数组的常见处理轻松拿下。

零、前言

对字符串(字符数组)的操作函数有很多,但是我们想要操作整型数组等呢:
这就需要内存操作函数了,memory在计算机科学中是内存的意思,这也是四大内存操作函数都有mem头的原因。
与void*类型指针重要的知识:

void*类型指针可以指向任何类型的数据,但是void*类型指针无法访问地址数据。这是因为指针压根就不知道它要访问多大空间,那么即使能访问得到的数据也毫无意义。因此不能对void*类型指针解引用操作,也不能做地址偏移±操作,这是语法型错误。

本文提及的字符串相关函数不熟悉的可以查看文章【C语言】<string.h>中十大字符串函数(用法+模拟实现)

一、memcpy 内存拷贝函数

我们知道strcpy(字符串拷贝函数)可以将一个字符串内容拷贝到另一个字符串,如果我们想拷贝整型数组,我们可以使用memcpy内存拷贝函数。
memcpy头文件:string.h
memcpy功能:逐字节地拷贝num个字节内存,并会覆盖原来内容。
memcpy函数声明:

void * memcpy ( void * dst, const void * src, size_t num );

src(source)源内存首字节地址,dst(destination)目标内存首字节地址。
num是需要拷贝的字节数。
memcpy返回的是dst首字节地址,并且是void*型,接收返回值需要强制类型转换。
memcpy使用实例:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[10] = { 0 };
	int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr1, arr2, 4 * sizeof(arr2[0]));
	for (int i = 0; i < 10; i++)
		printf("%d ", arr1[i]);
	printf("\n");
	memcpy(arr1+2,arr1,4*sizeof(arr1[0]));
	//理想拷贝后效果应该是1 2 1 2 3 4 0 0 0 0
	//实际输出是1 2 1 2 1 2 0 0 0 0 因为原来的3 4被提前覆盖了
	for (int i = 0; i < 10; i++)
		printf("%d ", arr1[i]);
	return 0;
}

注意:
dst和src都要有超过num个字节的空间。
源内存块src和目标内存块dst有任何的重叠,复制的结果都是未定义的。

代码输出实例

my_memcpy

void *my_memcpy(void *dst, const void *src, int num)
{
	//将void*型强制转化成char*,能够逐字节访问
	char *tmp_dst = (char *)dst;
	const char *tmp_src = (const char *)src;
	//循环num次,拷贝num个字节
	for (int i = 0; i < num; i++)
	{
		*tmp_dst++ = *tmp_src++;
	}
	return dst;
}

二、memmove 内存移动函数

memmove和memcpy的差别就是处理的源内存块和目标内存块是可以重叠的。
memmove头文件:string.h
memmove功能:将源内存块num个字节移动到目标内存块,源内存块内容并不消失。
memmove函数声明:

void * memmove ( void * dst, const void * src, size_t num );

src(source)源内存首字节地址,dst(destination)目标内存首字节地址。
num是需要拷贝的字节数。
memmove返回的是dst首字节地址,并且是void*型,接收返回值需要强制类型转换。
memmove使用实例:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[10] = { 0 };
	int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };

	memmove(arr1, arr2, 4 * sizeof(arr2[0]));
	printf("arr1:");
	for (int i = 0; i < 10; i++)
		printf("%d ", arr1[i]);

	printf("\narr2:");
	for (int i = 0; i < 10; i++)
		printf("%d ", arr2[i]);

	//理想拷贝后效果应该是1 2 1 2 3 4 0 0 0 0
	printf("\narr1自己移动自己:");
	memmove(arr1 + 2, arr1, 4 * sizeof(arr1[0]));
	for (int i = 0; i < 10; i++)
		printf("%d ", arr1[i]);

	return 0;
}

注意:dst和src都要有超过num个字节的空间。
代码输出实例

my_memmove

memmove的模拟关键在于解决内存块重复部分移动问题。
首先要比较dst和src两个地址大小,原因如下:

  • 如果要将1 2 3 4 5 6 7 8的src 1 2 3 4移动到dst 3 4 5 6位置,就要从4开始倒着移动,才能避免重叠部分移动错误。
  • 如果要将src 3 4 5 6移动到dst 1 2 3 4位置,就要从3开始正着移动。
  • 如果不是重叠内存块拷贝,正着倒着移动都一样,地址位置判断并不影响。
void *my_memmove(void *dst, const void *src, int num)
{
	char *tmp_dst = (char *)dst;
	const char *tmp_src = (const char *)src;
	
	if (src > dst)
		for (int i = 0; i < num; i++)
			*tmp_dst++ = *tmp_src++;
	else
		for (int i = num-1; i >=0; i--)
			*(tmp_dst + i) = *(tmp_src + i);
			
	return dst;
}

三、memset 内存赋值函数

memset头文件:string.h
memset功能:给一片内存num个字节赋上指定值
memset函数声明:

void * memset ( void * ptr, int value, size_t num );

ptr是内存块首字节地址。
value是要赋的值,这个值应该不超过一个无符号字节大小即28-1
num是需要赋值的字节数。
memset返回值是ptr首字节地址,并且是void*型,接收返回值需要强制类型转换。
memset使用实例:

#include <stdio.h>
#include <string.h>
int main()
{
	char carr[10] = { 0 };
	memset(carr, 65, sizeof(carr));
	printf("字符型:");
	for (int i = 0; i < 10; i++)
		printf("%c ",carr[i]);
		
	int darr[4] = { 0 };
	printf("\n整型:");
	memset(darr, 1,sizeof(darr));
	for (int i = 0; i < 4; i++)
		printf("%d ", darr[i]);
	return 0;
}

注意:
内存块要有num个字节大小。
给每个字节赋值,比如int型是4字节,赋值1,输出是16843009而不是1。

代码输出实例

my_memset

void *my_memset(void *ptr, int value, int num)
{
	//强制类型转换为无符号char*型
	unsigned char *tmp_ptr = (unsigned char *)ptr;
	//循环num次
	for (int i = 0; i < num; i++)
	{
		//因为char是特殊的整型,所以可以直接赋值
		*tmp_ptr++ = value;
	}

	return ptr;
}

四、memcmp 内存比较函数

memcmp头文件:string.h
memcmp功能:逐字节地比较从ptr1和ptr2开始的num个字节,直至比出。
memcmp函数声明:

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

ptr1是一个内存块首字节地址,ptr2是另一个内存块首字节地址。
num是需要比较的字节数。
ptr1>ptr2返回值>0,ptr1<ptr2返回值<0,完全相等返回值0。
memcmp使用实例:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[10] = { 1,2,3,4,5,7,7,7,7,7 };
	if (memcmp(arr1, arr2, sizeof(arr1)) > 0)
		printf("arr1大");
	else if (memcmp(arr1, arr2, sizeof(arr1)) < 0)
		printf("arr2大");
	else
		printf("arr1和arr2一样大");
	return 0;
}

注意:内存块要有num个字节大小。
代码输出实例

my_memcmp

int my_memcmp(const void *ptr1, const void *ptr2, int num)
{
	//先强制类型转换
	const char *tmp_ptr1 = (const char *)ptr1;
	const char *tmp_ptr2 = (const char *)ptr2;
	//循环num次,比较出就返回值
	for (int i = 0; i < num; i++)
	{
		if (*(tmp_ptr1+i) > *(tmp_ptr2+i))
			return 1;
		else if (*(tmp_ptr1+i) < *(tmp_ptr2+i))
			return -1;
	}
	//比较完num个字节了,仍没返回,完全相等返回值0。
	return 0;
}

熟练掌握库函数,敲码速度倍倍翻。
码字不容易,欢迎关注、点赞、收藏、评论、转发。

;