C语言中memcpymemmove等内存函数的介绍

Posted aaaaaaaWoLan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言中memcpymemmove等内存函数的介绍相关的知识,希望对你有一定的参考价值。

C语言中memcpy、memmove等内存函数的介绍

之前我们介绍了一些字符串操作函数 字符操作函数的介绍,c语言中字符串是被当做数组处理的,但那些函数只能实现对字符串的操作,有没有函数对其他类型的数组也能进行操作呢?

今天我们就介绍一些这样的函数

perror

稍微跑个题,在这里介绍一下perror

perror的函数原型:

image-20210613095031104

上篇博客讲到了strerror函数,将错误码errno作为参数传入strerror,函数会返回错误信息的地址,可以打印出来

而perrorr使用时不需要主动传errno,可在括号内写上自己想要添加的内容,打印错误信息时会加上所添加的内容,不同于strerror,strerror是将错误码转换为错误信息,不进行打印。

例:

strerror:

image-20210613095156468

perror:

image-20210613095215084

memcpy

memcpy函数介绍

函数原型:

void * memcpy ( void * destination, const void * source, size_t num ); 

函数返回的是目标空间的地址

上篇博客中我们讲到的strcpy只适用于字符串的拷贝,不适用于所有类型的元素,而memcpy适用于所有类型元素的拷贝,且可指定元素个数(字节个数)

类似于strncpy

函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。

这个函数在遇到 ‘\\0’ 的时候并不会停下来。

如果source和destination有任何的重叠,复制的结果都是未定义的。

也就是说,如果两块空间有重叠的部分,会导致拷贝失败

我们看一个使用例子:

#include <stdio.h> 

#include <string.h> 

struct { 

 char name[40]; 

 int age; 

} person, person_copy; 

int main () 

{ 

 char myname[] = "Pierre de Fermat"; 

 /* using memcpy to copy string: */ 

 memcpy ( person.name, myname, strlen(myname)+1 ); 
    //将myname的内容拷贝至结构体person的name中
    //strlen(myname)+1是表示把\\0也拷贝进去

 person.age = 46; 

 /* using memcpy to copy structure: */ 

 memcpy ( &person_copy, &person, sizeof(person) ); 
    //将结构体person的内容拷贝至person_copy

 printf ("person_copy: %s, %d \\n", person_copy.name, person_copy.age ); 

 return 0; 

} 

所以打印结果应该就是:person_copy: Pierre de Fermat, 46

image-20210613100124857

同样,我们对memcpy进行模拟实现:

既然memcpy可以接收任意类型的数据,那函数的参数就要设置为空类型,详细说明在作者的另外一篇文章中讲解***回调函数***模拟qsort的实现有提到 c指针汇总

void * 可以接收任意类型的地址,但不能进行解引用等运算操作,那怎么样对数据进行操作呢?每个类型的大小都不一样,同样,这个实现方式与qosrt的实现如出一辙,只需要将两个形参强转为char * 类型,之后进行解引用,对每个字节进行交换,这样就可以对任意类型的数据进行交换,而第三个参数又是要交换的字节个数,所以这里有一个不好的地方就是使用者需要准确写出要拷贝内容的字节个数,否则容易出现错误。下面看代码:

void* my_memcpy(void* dst, const void* src, size_t num)//用void*类型的指针接收各种类型的指针
{
	assert(dst && src);
	void* start = dst;
	while (num--)//循环num次,交换num个字节
	{
		*(char*)dst = *(char*)src;//因为不确定要交换的元素类型,所以用char*指针强转,然后一个字节一个字节地进行交换
		dst = (char*)dst + 1;//交换完后,要使地址加1
		src = (char*)src + 1;
	}
	return start;
}

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

有一个地方需要注意:

*(char*)dst = *(char*)src;//因为不确定要交换的元素类型,所以用char*指针强转,然后一个字节一个字节地进行交换
		dst = (char*)dst + 1;//交换完后,要使地址加1
		src = (char*)src + 1;

这一段不能写成*(char*)dst++ = (char)src++;

强转只是一种临时状态,等到++时效果可能已经没有了,这要看编译器如何处理

但是这种实现方式不能完成目标空间与源空间重叠时的拷贝

void* my_memcpy(void* dst, const void* src, size_t num)//用void*类型的指针接收各种类型的指针
{
	assert(dst && src);
	void* start = dst;
	while (num--)//循环num次,交换num个字节
	{
		*(char*)dst = *(char*)src;//因为不确定要交换的元素类型,所以用char*指针强转,然后一个字节一个字节地进行交换
		dst = (char*)dst + 1;//交换完后,要使地址加1
		src = (char*)src + 1;
	}
	return start;
}

//

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

这段代码的本意是想把12345拷贝至34567的位置,即期望结果是:1,2,1,2,3,4,5,8,9,10

但实际情况:

image-20210613103413437

结果并不如预期

我们再使用库函数实现一下:

image-20210613103646068

发现结果是我们想要的,这是为什么呢?

我们接着看,讲解memmove时将这些问题解决

memmove

memmove函数介绍

函数原型:

void * memmove ( void * destination, const void * source, size_t num ); 

与memcpy函数的参数类型及返回类型都相同

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

如果源空间和目标空间出现重叠,就得使用memmove函数处理。

#include <stdio.h> 

#include <string.h> 

int main () 

{ 

 char str[] = "memmove can be very useful......"; 

 memmove (str+20,str+15,11); //从u开始拷贝v之后的11个字符

 puts (str); 

 return 0; 

} 

结果应为:memmove can be very very useful.

image-20210613104042009

当然,使用库函数memcpy也是这个结果

我们先对memmove模拟实现

该拷贝有两种情况:

一种是源地址大于目标地址,一种是源地址小于目标地址

不同情况采取的拷贝方式也就不同

但前提是目标空间与源空间重合

我们实现memcpy时是对源空间从前向后依次把数据拷贝至目标空间

下面我们来分析情况不同时拷贝方式的不同:

image-20210613104304162

image-20210613104347517

源地址大于目标地址:

image-20210613104417495

image-20210613104436366

上面所说的从后向前拷贝,从前向后拷贝,都是指源空间,实际拷贝时,源空间后面的字节还是照样赋给对应目标空间后面的字节,源空间前面的字节还是照样赋给对应目标空间前面的字节

其实还有一种情况:

image-20210613104507997

该情况等我们实现好代码后自然就明白了

void* my_memmove(void* dst, void* src, size_t num)
{
	assert(dst && src);
	void* start = dst;
	if (src > dst)  //源地址大于目标地址
	{
		while (num--)//与实现memcpy相同
		{
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;
			src = (char*)src + 1;
		}
	}
	if (dst > src) //目标地址大于源地址
	{
		while (num--)//从最后一个元素的最后一个字节进行拷贝,一直到第一个元素的第一个字节
		{
			*((char*)dst + num) = *((char*)src + num);
		}
	}
	return start;
}

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

所以第三种情况出现时, * ((char * )dst + num) = * ((char * )src + num)会造成数组越界,如果数组之后的内存不够大,会造成一定的麻烦。

注:有的编译器提供的memcpy函数功能与memmove相同,即可以拷贝源内存和目标内存重叠时的空间,如vs2019

所以我们就知道为什么使用库函数memcpy可以实现memmove的功能了

不同情况实现拷贝的方法也不同,所以我们模拟实现的memcpy不能实现空间重叠时数据的拷贝

memcmp

memcmp介绍

函数原型:

int memcmp ( const void * ptr1, 

 const void * ptr2, 

 size_t num ); 

函数参数及返回类型与上面两个函数相同

memcmp可以比较从ptr1和ptr2指针开始的num个字节

返回值如下:

image-20210613105651070

使用:

#include <stdio.h> 

#include <string.h> 

int main () 

{ 

 char buffer1[] = "DWgaOtP12df0"; 

 char buffer2[] = "DWGAOTP12DF0"; 

 int n; 

 n=memcmp ( buffer1, buffer2, sizeof(buffer1) ); 

 if (n>0) printf ("'%s' is greater than '%s'.\\n",buffer1,buffer2); 

 else if (n<0) printf ("'%s' is less than '%s'.\\n",buffer1,buffer2); 

 else printf ("'%s' is the same as '%s'.\\n",buffer1,buffer2); 

 return 0; 

} 

观察两个数组,发现g和G不同,g的ASCII码值大于G的ASCII码值,所以结果应该是’%s’ is greater than ‘%s’.

image-20210613110346704

同样,对memcmp进行模拟实现,其比较方式与qsort函数中compare函数的实现也十分相似:

int     my_memcmp(void* buf1, void* buf2, int count)
   {
	if (!count)//当count等于0时,直接返回0
		return(0);
	while (--count && *(char*)buf1 == *(char*)buf2)//一个字节相等且count!= 0时,地址往后加1个字节
	{
		buf1 = (char*)buf1 + 1;
		buf2 = (char*)buf2 + 1;
	}
	return(*(char*)buf1 - *(char*)buf2);//到最后一个字节时,直接返回俩个字节内存中值的差
}

int main()
{
	float arr1[20] = { 1.0,2.0,3.0,5.0,7.0 };
	float arr2[20] = { 1.0,2.0,3.0,4.0,5.0 };
	int ret = my_memcmp(arr1, arr2, 12);
	printf("%d\\n", ret);
	return 0;
}

memset

memset函数介绍

函数原型:

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

memset的函数参数是一个空类型指针接收数组,value是指要修改成的值,num同样还是字节个数

memset是以字节为单位设置内存

所以如果设置int的内存,结果并不能达到全1

例:

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

image-20210613110642567

因为1在内存中按十六进制表示的话是01,所以给int类型设置内存,就是每一个内存都设置成01

模拟实现memset:

void* mymemset(void* ptr, int value, size_t num)
{
	assert(ptr);
	void* start = ptr;
	if (!num)
		return 0;
	while (num--)
	{
		*(char*)ptr = (char)value;//设置每个字节的大小
        //对value进行强转,否则会有数据的丢失
		ptr = (char*)ptr + 1;
	}
	return start;
}

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

以上就是对一些内存函数的简单介绍,希望对大家有帮助。

以上是关于C语言中memcpymemmove等内存函数的介绍的主要内容,如果未能解决你的问题,请参考以下文章

C语言学习内存操作函数之------->memcpy memmove 详解与手动实现

C语言学习内存操作函数之------->memcpy memmove 详解与手动实现

内存操作函数之------->memcpy memmove 详解与手动实现

C语言中内存分配 (转)

C语言中内存分配

C语言中动态内存分配函数的用法及作用?(比如malloc,calloc,realloc等)