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

       终于找了个机会可以写下这两个函数的区别了,不然怕是要忘了,另外,感觉C语言中的小的知识点不要太多,仍需要自己不断地编程,不断地去发掘、、、

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)有重叠,内存重叠有两种表现形式,如下图所示:
在这里插入图片描述
对于覆盖情况一:
       此时将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* memmove(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;
}

② memmove()函数:

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()的代码逻辑,转换的很巧妙!

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX C函数库API 1.字符测试篇 15 1.1 15 isalnum(测试字符是否为英文或数字) 15 1.2 15 isalpha (测试字符是否为英文字母) 15 1.3 16 isascii(测试字符是否为ASCII 码字符) 16 1.4 17 iscntrl(测试字符是否为ASCII 码的控制字符) 17 1.5 17 isdigit(测试字符是否为阿拉伯数字) 17 1.6 17 isgraphis(测试字符是否为可打印字符) 17 1.7 18 islower(测试字符是否为小写字母) 18 1.8 18 isprint(测试字符是(否为可打印字符) 18 1.9 19 isspace(测试字符是否为空格字符) 19 1.10 20 ispunct(测试字符是否为标点符号或特殊符号) 20 1.11 20 isupper(测试字符是否为大写英文字母) 20 1.12 21 isxdigit(测试字符是否为16进制数字) 21 2.字符串转换篇 21 2.1 21 atof(将字符串转换成浮点型数) 21 2.2 22 atoi(将字符串转换成整型数) 22 2.3 22 atol(将字符串转换成长整型数) 22 2.4 23 gcvt(将浮点型数转换为字符串,取四舍五入) 23 2.5 24 strtod(将字符串转换成浮点数) 24 2.6 24 strtol(将字符串转换成长整型数) 24 2.7 25 strtoul(将字符串转换成无符号长整型数) 25 2.8 25 toascii(将整型数转换成合法的ASCII 码字符) 25 2.9 26 tolower(将大写字母转换成小写字母) 26 2.10 26 toupper(将小写字母转换成大写字母) 26 3.内存控制篇 27 3.1 27 calloc(配置内存空间) 27 3.2 28 free(释放原先配置的内存) 28 3.3 28 getpagesize(取得内存分页大小) 28 3.4 28 malloc(配置内存空间) 28 3.5 28 mmap(建立内存映射) 28 3.6 30 munmap(解除内存映射) 30 4.日期时间篇 31 4.1 31 asctime(将时间和日期以字符串格式表示) 31 4.2 31 ctime(将时间和日期以字符串格式表示) 31 4.3 32 gettimeofday(取得目前的时间) 32 4.4 33 gmtime(取得目前时间和日期) 33 4.5 34 localtime(取得当地目前时间和日期) 34 4.6 34 mktime(将时间结构数据转换成经过的秒数) 34 4.7 35 settimeofday(设置目前时间) 35 4.8 35 time(取得目前的时间) 35 5.内存及字符串操作篇 36 5.1 36 bcmp(比较内存内容) 36 5.2 36 bcopy(拷贝内存内容) 36 5.3 37 bzero(将一段内存内容全清为零) 37 5.4 37 index(查找字符串中第一个出现的指定字符) 37 5.5. 37 memccpy(拷贝内存内容) 37 5.6 38 memchr(在某一内存范围中查找一特定字符) 38 5.7 38 memcmp(比较内存内容) 38 5.8 39 memcpy拷贝内存内容) 39 5.9 40 memmove(拷贝内存内容) 40 5.10 40 memset(将一段内存空间填入某值) 40 5.11 40 rindex(查找字符串中最后一个出现的指定字符) 40 5.12 41 strcasecmp(忽略大小写比较字符串) 41 5.13 41 strcat(连接两字符串) 41 5.14 42 strchr(查找字符串中第一个出现的指定字符) 42 5.15 42 strcmp(比较字符串) 42 5.16 43 strcoll(采用目前区域的字符排列次序来比较字符串) 43 5.17 43 strcpy(拷贝字符串) 43 5.18 44 strcspn(返回字符串中连续不含指定字符串内容的字符数) 44 5.19 44 strdup(复制字符串) 44 5.20 45 strlen(返回字符串长度) 45 5.21 45 strncasecmp(忽略大小写比较字符串) 45 5.22 46 strncat(连接两字符串) 46 5.23 46 strncpy(拷贝字符串) 46 5.24 47 strpbrk(查找字符串中第一个出现的指定字符) 47 5.25 47 strrchr(查找字符串中最后出现的指定字符) 47 5.26 47 strspn(返回字符串中连续不含指定字符串内容的字符数) 47 5.27 48 strstr(在一字符串中查找指定的字符串) 48 5.28 48 strtok(分割字符串) 48 6. 常用数学函数篇 49 6.1 49 abs(计算整型数的绝对值) 49 6.2 49 acos(取反余弦函数数值) 49 6.3 50 asin(取反正弦函数值) 50 6.4 50 atan(取反正切函数值) 50 6.5 51 atan2(取得反正切函数值) 51 6.6 51 ceil(取不小于参数的最小整型数) 51 6.7 52 cos(取余玄函数值) 52 6.8 52 cosh(取双曲线余玄函数值) 52 6.9 53 exp(计算指数) 53 6.10 53 frexp(将浮点型数分为底数与指数) 53 6.11 54 ldexp(计算2的次方值) 54 6.12 54 log(计算以e 为底的对数值) 54 6.13 55 log10(计算以10 为底的对数值) 55 6.14 55 pow(计算次方值) 55 6.15 56 sin(取正玄函数值) 56 6.16 56 sinh(取双曲线正玄函数值) 56 6.17 56 sqrt(计算平方根值) 56 6.18 57 tan(取正切函数值) 57 6.19 57 tanh(取双曲线正切函数值) 57 7.用户组篇 58 7.1 58 endgrent(关闭组文件) 58 7.2 58 endpwent(关闭密码文件) 58 7.3 58 endutent(关闭utmp 文件) 58 7.4 59 fgetgrent(从指定的文件来读取组格式) 59 7.5 60 fgetpwent(从指定的文件来读取密码格式) 60 7.6 61 getegid(取得有效的组识别码) 61 7.7 61 geteuid(取得有效的用户识别码) 61 7.8 62 getgid(取得真实的组识别码) 62 7.9 62 getgrent(从组文件中取得账号的数据) 62 7.10 63 getgrgid(从组文件中取得指定gid 的数据) 63 7.11 64 getgrnam(从组文件中取得指定组的数据) 64 7.12 64 getgroups(取得组代码) 64 7.13 65 getpw(取得指定用户的密码文件数据) 65 7.14 66 getpwent(从密码文件中取得账号的数据) 66 7.15 67 getpwnam(从密码文件中取得指定账号的数据) 67 7.16 68 getpwuid(从密码文件中取得指定uid 的数据) 68 7.17 68 getuid(取得真实的用户识别码) 68 7.18 69 getutent(从utmp 文件中取得账号登录数据) 69 7.19 70 getutid(从utmp 文件中查找特定的记录) 70 7.20 71 getutline(从utmp 文件中查找特定的记录) 71 7.21 71 initgroups(初始化组清单) 71 7.22 71 pututline(将utmp 记录写入文件) 71 7.23 72 seteuid(设置有效的用户识别码) 72 7.24 72 setfsgid(设置文件系统的组识别码) 72 7.25 73 setfsuid(设置文件系统的用户识别码) 73 7.26 73 setgid(设置真实的组识别码) 73 7.27 73 setgrent(从头读取组文件中的组数据) 73 7.28 74 setgroups(设置组代码) 74 7.29 74 setpwent(从头读取密码文件中的账号数据) 74 7.30 75 setregid(设置真实及有效的组识别码) 75 7.31 75 setreuid(设置真实及有效的用户识别码) 75 7.32 75 setuid(设置真实的用户识别码) 75 7.33 76 setutent(从头读取utmp 文件中的登录数据) 76 7.34 76 utmpname(设置utmp 文件路径) 76 8.数据结构和算法篇 76 8.1 76 crypt(将密码或数据编码) 76 8.2 77 bsearch(二元搜索) 77 8.3 78 lfind(线性搜索) 78 8.4 79 lsearch(线性搜索) 79 8.5 80 qsort(利用快速排序法排列数组) 80 8.6 81 rand(产生随机数) 81 8.7 81 srand(设置随机数种子) 81 9 文件操作篇 82 9.1 82 close(关闭文件) 82 9.2 82 creat(建立文件) 82 9.3 83 dup(复制文件描述词) 83 9.4 83 dup2(复制文件描述词) 83 9.5 84 fcntl(文件描述词操作) 84 9.6 85 flock(锁定文件或解除锁定) 85 9.7 85 fsync(将缓冲区数据写回磁盘) 85 9.8 85 lseek(移动文件的读写位置) 85 9.9 86 mkstemp(建立唯一的临时文件) 86 9.10 86 open(打开文件) 86 9.11 88 read(由已打开的文件读取数据) 88 9.12 89 sync(将缓冲区数据写回磁盘) 89 9.13 89 write(将数据写入已打开的文件内) 89 10 文件内容操作篇 89 10.1 89 clearerr(清除文件流的错误旗标) 89 10.2 90 fclose(关闭文件) 90 10.3 90 fdopen(将文件描述词转为文件指针) 90 10.4 90 feof(检查文件流是否读到了文件尾) 90 10.5 91 fflush(更新缓冲区) 91 10.6 91 fgetc(由文件中读取一个字符) 91 10.7 91 fgets(由文件中读取一字符串) 91 10.8 92 fileno(返回文件流所使用的文件描述词) 92 10.9 92 fopen(打开文件) 92 10.10 93 fputc(将一指定字符写入文件流中) 93 10.11 94 fputs(将一指定的字符串写入文件内) 94 10.12 94 fread(从文件流读取数据) 94 10.13 95 freopen(打开文件) 95 10.14 95 fseek(移动文件流的读写位置) 95 10.15 96 ftell(取得文件流的读取位置) 96 10.16 96 fwrite(将数据写至文件流) 96 10.17 97 getc(由文件中读取一个字符) 97 10.18 97 getchar(由标准输入设备内读进一字符) 97 10.19 98 gets(由标准输入设备内读进一字符串) 98 10.20 98 mktemp(产生唯一的临时文件名) 98 10.21 99 putc(将一指定字符写入文件中) 99 10.22 99 putchar(将指定的字符写到标准输出设备) 99 10.23 99 rewind(重设文件流的读写位置为文件开头) 99 10.24 99 setbuf(设置文件流的缓冲区) 99 10.25 100 setbuffer(设置文件流的缓冲区) 100 10.26 100 setlinebuf(设置文件流为线性缓冲区) 100 10.27 100 setvbuf(设置文件流的缓冲区) 100 10.28 101 ungetc(将指定字符写回文件流中) 101 11 进程操作篇 101 11.1 101 atexit(设置程序正常结束前调用的函数) 101 11.2 101 execl(执行文件) 101 11.3 102 execlp(从PATH 环境变量中查找文件并执行) 102 11.4 102 execv(执行文件) 102 11.5 103 execve(执行文件) 103 11.6 104 execvp(执行文件) 104 11.7 104 exit(正常结束进程) 104 11.8 104 _exit(结束进程执行) 104 11.9 105 vfork(建立一个新的进程) 105 11.10 105 getpgid(取得进程组识别码) 105 11.11 106 getpgrp(取得进程组识别码) 106 11.12 106 getpid(取得进程识别码) 106 11.13 107 getppid(取得父进程的进程识别码) 107 11.14 107 getpriority(取得程序进程执行优先权) 107 11.15 108 nice(改变进程优先顺序) 108 11.16 108 on_exit(设置程序正常结束前调用的函数) 108 11.17 109 setpgid(设置进程组识别码) 109 11.18 109 setpgrp(设置进程组识别码) 109 11.19 109 setpriority(设置程序进程执行优先权) 109 11.20 110 system(执行shell 命令) 110 11.21 110 wait(等待子进程中断或结束) 110 11.22 111 waitpid(等待子进程中断或结束) 111 11.23 112 fprintf(格式化输出数据至文件) 112 11.24 112 fscanf(格式化字符串输入) 112 ... ... ... ...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值