现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

Linux下C语言内存拷贝函数memcpy/memmove

2019-08-09 21:58 工业·编程 ⁄ 共 3241字 ⁄ 字号 暂无评论

1. 函数介绍

       说到memcpy()和memmove()这两个函数,可能大家从名称上认为二者是两个不同的函数。其实不然,事实上,这两个函数功能是类似的,都是对内存进行拷贝(千万不要被memmove()函数中的move给欺骗了,不要想当然的认为它就是移动),二者的区别仅仅是对于内存重叠这一现象的处理。
       如果要拷贝的两个内存空间不重叠的话,那么使用memcpy()和memmove()是等价的!

首先先来看看两个函数的函数原型。
二者函数原型如下:

//dest:拷贝到的目的地址 src:拷贝的起始地址 n:表示拷贝多少个字节

void *memcpy(void *dest, const void *src, size_t n);

void *memmove(void *dest, const void *src, size_t n);

       咋一看,这两个函数除了函数名不同,函数的形参列表以及返回值均相同。然后,当你知道两者的函数定义时,你就会理解上面那句“如果要拷贝的两个内存空间不重叠的话,那么使用memcpy()和memmove()是等价的!”话的原因了,这里先不直接列出两者的函数定义,而是先讲一下内存重叠的问题。

2. 内存重叠

       首先要知道什么是内存重叠?它是指要拷贝的起始地址(即src)与要拷贝到的目的地址(即dest)有重叠,内存重叠有两种表现形式,如下图所示:
wps1
对于覆盖情况一
       此时将src拷贝到dest,不会出现差错。我们可以一位一位的进行拷贝,首先src中的1拷贝到dest的1的位置,2拷贝到2的位置,3拷贝到3的位置,然后此时4就拷贝的4的位置,但此时src处位置1处就不再是1了,而是变成了4。同样,src中位置2处就变成了5。
       至此便完成了5个位的内存拷贝,本次内存拷贝的结果是正确的,将“12345”五个数拷贝到了目的地址上。但是此时源地址上相应的五个数字中的前两个数字分别被替换成了“4”和“5”。

对于覆盖情况二
       此时将此时将src拷贝到dest,就会发生错误了。同样我们还是一位一位的进行拷贝。首先将src中的1拷贝到dest的1的位置,但dest中位置1处对应的是src的位置4,这时src位置4处的值4就被修改为1!同样将src中的2拷贝到dest中位置2处,但dest中位置2处对应的是src的位置5,这时src位置4处的值5就被修改为2!接下来在进行按位拷贝的话,将会得到错误的结果“12312”。
       事实上操作系统对于第二种覆盖情况是未定义的,我们按位拷贝获得的结果只是用来认识第二种覆盖情况,但在代码中碰到这种情况,得到的结果是未知的,不一定是“12312”,可以自行编写程序进行验证。

       对于第一种覆盖情况,使用memcpy()和memmove()均不会出现拷贝出错的情况,但memcpy()不能正确处理第二种情况,只能使用memmove()才能进行正确的内存拷贝这样一来,在不知道内存是否重叠的情况下,为了保证内存拷贝的正确执行,使用memmove()是最为稳妥的,但作为牺牲,程序的执行效率会比memcpy()要低(函数memmove()的定义要比memcpy()的定义复杂,这个区别在最后阐述)。因此,在我们能够保证内存不会重叠的前提下,使用memcpy()会更高效。一般而言,内存一般是不会重叠的,但有时在不经意间,我们代码中所使用的一些操作就会导致内存重叠。

3. 实例验证

下面举个例子来做一个简单说明。
注:最好将拷贝过程在纸上画出来,这样会加深理解

//首先定义一个字符串数组str

char str[11] = "0123456789";

/*

下面分别使用memmove和memcpy对这个数组进行操作

① 覆盖情况一(src是高地址,dest是低地址,由高地址向低地址进行内存拷贝)

*/

memmove((void *)&str[0], (void *)&str[3], 5);

memcpy((void *)&str[0], (void *)&str[3], 5);

/*

两个函数的结果是相同的,得到的结果均为“3456756789”

即实现了将“34567”这五个数字拷贝到数组的前五个位置上

*/

/*

② 覆盖情况二(src是低地址,dest是高地址,由低地址向高地址进行内存拷贝)

*/

memmove((void *)&str[3], (void *)&str[0], 5);

memcpy((void *)&str[3], (void *)&str[0], 5);

/*

这次,两个函数的结果完全不同,

使用memmove()得到结果“0120123489”是我们事先想得到的,

而memcpy()得到的结果却不是我们所预期的,结果是“0120120189”

*/

       以上例子需要我们注意的是,我们在对数组操作的时候,容易导致内存重叠,从而导致我们得到错误的结果。所以在以后的编程中需要注意这一点。

测试代码如下:

#include <stdio.h>

#include <string.h>

int main(int argc, char *argv[])

{

    int i;

    char str[11] = "0123456789";

    for (i = 0; i < 10; i++)

    {

        printf("%c", str[i]);

    }

    printf("\n");

    //memmove((void *)&str[3], (void *)&str[0], 5);

    //memcpy((void *)&str[3],(void *)&str[0], 5);

    //memmove((void *)&str[0],(void *)&str[3], 5);

    memcpy((void *)&str[0], (void *)&str[3], 5);

    for (i = 0; i < 10; i++)

    {

        printf("%c", str[i]);

    }

    printf("\n");

    return 0;

}

4. 函数定义

① memcpy()函数:

void* memcpy(void* str1,const void* str2,size_t n)

{

size_t i;

    char *pStr1 = (char *)str1;

    char *pStr2 = (char *)str2;

    for(size_t i = 0;i != n; i++)

    {

     *(pStr1++) = *(pStr2++);

    }

    return str1;

}

void* memmove(void* str1,const void* str2,size_t n)

{

size_t i;

    char *pStr1 = (char *)str1;

    char *pStr2 = (char *)str2;

    if  (pStr1 < pStr2)

    {

        for(size_t i = 0;i != n; i++)

        {

            *(pStr1++) = *(pStr2++);

        }

    }

    else

    {

        pStr1 += n-1;

        pStr2 += n-1;

        for(size_t i = 0;i != n; i++)

        {

            *(pStr1--) = *(pStr2--);

        }

    }

    return str1;

}

       可见memcpy()函数的函数定义是memmove()函数的函数定义的一部分,这也是为什么在内存不重叠以及第一种重叠情况(src是高地址,dest是低地址,由高地址向低地址进行内存拷贝)下均可使用memcpy()和memmove()函数的原因了。但在第二种重叠情况下(src是低地址,dest是高地址,由低地址向高地址进行内存拷贝),就只能使用memmove()函数了,可以仔细看一下memmove()的代码逻辑,转换的很巧妙!

给我留言

留言无头像?