目录
memcpy
函数介绍:
函数解析:
memcpy函数复制的数据长度
内存重叠
凑不出元素的字节数
模拟memcpy
memmove
函数介绍:
内存重叠的思考、优化与模拟
模拟memove
memcpy函数是一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)。
其原型是:
void * memcpy ( void * destination, const void * source, size_t num );
作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。
函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。
使用memcpy函数时,需要注意:
- memcpy拷贝的是空间 ,也就是字节数(1byte = 8bit)。
- 使用的原理与strncpy类似,但还是有不同之处,列如:最后的参数不同,strncpy最后的num表示的是宽度,而memcpy表示的是字节数。
- 以及,memcpy的前两个参数的类型是void* 表示memcpy可以调整任意类型的内存,或是说任意类型可以使用它。
int arr1[] = { 1, 2, 3, 4, 5, 6, 7 };
int arr2[10] = { 0 };
memcpy(arr2,arr1,28);
使用memcpy函数时,特别要注意数据长度。如果复制的数据类型是char,那么数据长度就等于元素的个数。而如果数据类型是其他(如int, double, 自定义结构体等),就要特别注意数据长度的值。
不过,无论拷贝何种数据类型,都用
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}
但真的是这样吗?
通过上图,我们得知,因为20个字节数,我们需要复制的元素包含了1、2、3、4、5,而又因为arr1+2所在位置表示的元素是3,因在复制的过程中元素3被覆盖了,变为了元素1,元素4也是如此被覆盖成了元素2,因而当进行后续复制时,得到的结果与我们想象中的结果发生了偏移。
- 结论:memcpy并不能用来处理内存重叠的部分,若要处理内存重叠的部分则需要使用memmove函数 。
- 注意:在VS编译器中,memcpy的功能涵盖了memmove,但memcpy在C语言标准中是不能处理内存重叠的部分。
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。
注:这里的存储单元是指二进制形式
由于目标地址是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 }
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字节的原理,所以在此处更为合适使用!
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
memcpy(arr1+2,arr1,20);
以上代码我们在memcpy中已经分析过了,因为原空间与目标空间发生了部分重叠,导致了在拷贝的时候使得部分重叠的空间被覆盖,变为了新的元素,但是如诺我们换一种拷贝的方式呢?
原拷贝方式:
如图所示,这是从前往后拷贝,但如果我们从后往前拷贝呢?
如上图所示,这是从后往前拷贝,且并未发生元素覆盖,那么memmove的原理就是从后往前拷贝吗?
答案并不是!
倘若我们的目标空间和原空间发生改变,结果就是不一样的了。
如上图,就是从后往前进行拷贝,但结果却还是元素被覆盖了,但是从前往后拷贝却正确了,所以我们得出结论:
目标空间在原空间之前的,使用从前往后的方式进行拷贝。
目标空间在原空间之后的,使用从后往前的方式进行拷贝。
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是不用动的。