memcpy 与 memmove

目录

memcpy

函数介绍:

函数解析:

memcpy函数复制的数据长度

内存重叠

凑不出元素的字节数 

模拟memcpy 

 memmove

函数介绍:

内存重叠的思考、优化与模拟

模拟memove


memcpy

函数介绍:

memcpy函数是一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)。

其原型是:

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

作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。

函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。

使用memcpy函数时,需要注意:

  • 数据长度(第三个参数)的单位是字节。
  • 注意该函数有一个返回值,类型是void*,是一个指向destin的指针。
  • 这个函数在遇到 '\0' 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的。

函数解析:

  • memcpy拷贝的是空间 ,也就是字节数(1byte = 8bit)。
  • 使用的原理与strncpy类似,但还是有不同之处,列如:最后的参数不同,strncpy最后的num表示的是宽度,而memcpy表示的是字节数。
  • 以及,memcpy的前两个参数的类型是void* 表示memcpy可以调整任意类型的内存,或是说任意类型可以使用它。

memcpy函数复制的数据长度

int arr1[] = { 1, 2, 3, 4, 5, 6, 7 };
int arr2[10] = { 0 };
memcpy(arr2,arr1,28);

使用memcpy函数时,特别要注意数据长度。如果复制的数据类型是char,那么数据长度就等于元素的个数。而如果数据类型是其他(如int, double, 自定义结构体等),就要特别注意数据长度的值。

memcpy 与 memmove_第1张图片

不过,无论拷贝何种数据类型,都用 n * sizeof(type_name)的写法。  

    char a[10] = "abcdefgh";
    unsigned n = 2;
    void * p = memcpy(a+3, a, n);

 以上代码将从a开始的两个字节的数据(即’a’和’b’),复制到从a+3开始的内存('d’所在的地址)。这样,'d’和’e’被替换。执行结束之后,字符数组(字符串)a的内容变为"abcabfgh"。

内存重叠

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

以上代码将1开始的20个字节复制到arr1+2开始的内存,也就是从arr[2]开始的20个字节。这样原先的内容变为了,{1,2,1,2,3,4,5,8,9,10}

但真的是这样吗?

memcpy 与 memmove_第2张图片

 

通过上图,我们得知,因为20个字节数,我们需要复制的元素包含了1、2、3、4、5,而又因为arr1+2所在位置表示的元素是3,因在复制的过程中元素3被覆盖了,变为了元素1,元素4也是如此被覆盖成了元素2,因而当进行后续复制时,得到的结果与我们想象中的结果发生了偏移。

memcpy 与 memmove_第3张图片

  • 结论:memcpy不能用来处理内存重叠的部分,若要处理内存重叠的部分则需要使用memmove函数 。
  • 注意:在VS编译器中,memcpy的功能涵盖了memmove,但memcpy在C语言标准中是不能处理内存重叠的部分。

memcpy 与 memmove_第4张图片

凑不出元素的字节数 

    int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    unsigned n = 5;
    void * p = memcpy(a+3, a, n);

以上代码将从a开始的5个字节的数据复制。

5个字节的数据是什么呢?前四个字节组成了一个完整的int(即第一个元素0),int类型的长度是4个字节。

第五个字节,只能取到第二个元素的第1个字节。这里又会涉及小端方式储存,那么读到的是元素1的低8位,写成十六进制即0x1。 

memcpy 与 memmove_第5张图片

注:这里的存储单元是指二进制形式

由于目标地址是a+3。而因为a+3 = &a[3],所以只的是下标为3的元素,也就是元素3被替换为0。元素4写成十六进制是0x0004,低8位被替换为0x1,变为0x0001。 

所以执行结束之后,数组a的内容变为 { 0, 1, 2, 0, 1, 5, 6, 7, 8, 9 }

memcpy 与 memmove_第6张图片

模拟memcpy 

void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src); 
	void* ret = dest;
	while (num--)//进行一个字节一个字节的拷贝复制
	{
		*(char*)dest = *(char*)src; 
		dest = (char*)dest + 1; 
		src = (char*)src + 1;
	}
	return ret;

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

 注:两个参数强制转化为char*类型是为了一个字节一个字节的进行拷贝复制,因为char*的数据特点和长度是1字节的原理,所以在此处更为合适使用!

memcpy 与 memmove_第7张图片

 memmove

函数介绍:

memcpy 与 memmove_第8张图片

  • 其原型和memcpy一致。
  • 和memcpy的差别就是memmove函数处理的源内存块和⽬标内存块是可以重叠的。 
  • 如果源空间和⽬标空间出现重叠,就得使⽤memmove函数处理。

内存重叠的思考、优化与模拟

 

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

以上代码我们在memcpy中已经分析过了,因为原空间与目标空间发生了部分重叠,导致了在拷贝的时候使得部分重叠的空间被覆盖,变为了新的元素,但是如诺我们换一种拷贝的方式呢?

原拷贝方式:

memcpy 与 memmove_第9张图片

 如图所示,这是从前往后拷贝,但如果我们从后往前拷贝呢?

memcpy 与 memmove_第10张图片

如上图所示,这是从后往前拷贝,且并未发生元素覆盖,那么memmove的原理就是从后往前拷贝吗?

答案并不是!

倘若我们的目标空间和原空间发生改变,结果就是不一样的了。

 memcpy 与 memmove_第11张图片

如上图,就是从后往前进行拷贝,但结果却还是元素被覆盖了,但是从前往后拷贝却正确了,所以我们得出结论:

目标空间在原空间之前的,使用从前往后的方式进行拷贝。

目标空间在原空间之后的,使用从后往前的方式进行拷贝。

memcpy 与 memmove_第12张图片

模拟memove

void * my_memmove (void* dest,const void* src,size_t num)
{
	assert(dest && src);
	if (dest < src)
	{
		//前->后
		while (num--)
		{
			*(char*)dest = *(char*)src; 
            dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		//后->前
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
}
void test3()
{
	int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	my_memmove(arr1, arr1 + 2, 20);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d", arr1[i]);
	}
}
int main()
{
	test3();
	return 0;
}

从后往前 :

 *((char*)dest + num) = *((char*)src + num);

  • 强制类型转化为char后+num,得到最后一个字节,从后往前是从最后一个字节开始进行挪动的,所以要从最后一个字节开始,且num在循环中是while(num--)的所以这里的dest和src是不用动的。

memcpy 与 memmove_第13张图片

你可能感兴趣的:(C语言,c语言,指针,数组,函数)