C语言之memcpy函数
昨天自己动手实现memcpy这个函数,用一个例程试了一下,结果正确,满心欢心,可是有些地方想不明白,于是百度了一下,结果自己写的函数简直无法直视。
觉得还是写个总结,以示教训。
先贴上我自己的函数:
- char *mymemcpy(char *dest, const char * src, int n)
- {
- char *pdest;
- char *psrc;
- pdest = dest;
- psrc = src;
- for(n; n>0; n--)
- {
- *pdest = *psrc;
- pdest++;
- psrc++;
- }
- }
我这个程序只能是在非常理想的条件下才能完成复制任务,一旦参数有误,那么就会运行出错;另外没有返回值,而是直接将dest指针作为了返回型参数。
另一点需要注意:在函数体中我另外声明了两个指针,分别指向dest和src,我是这样想的:由于循环中要移动指针,为了不影响主程序中实参指针的位置,所以我认为需要重新定义两个指针。后来我发现,我这样想是错误的,指针变量实质上也是变量,指针作为形参,那么这个指针也是实实在在存在的,那么在函数中改变这个形参指针的位置并不会影响主程序中实参指针的位置。所以在这个函数中,没必要重新声明两个指针,最好是在返回值中返回一个指向dest的指针就够了。
我会犯上述这个错误,应该是受了“地址传参”和“值传参”的影响。由于地址传参太过于强调函数可以改变主程序中的数据内容,以至于让我把形参指针和实参指针等同起来了。实质上形参指针变量位置的改变并不会影响实参指针的位置。
重要的内容写前面,
自己总结该函数的几个要点:
1、参数判断:对参数的合法性进行判断
2、声明中间变量:由于要返回目的指针,所以需要保留目的首地址;最好是不要破坏形参,设置临时变量替换
3、void *类型:要注意dest和src的类型可能不同,进而造成dest++ src++不匹配的问题,先强制类型转换
4、void 类型做右值:void类型变量或是返回值为void类型的函数,一旦做右值编译出错
5、指针形参:即上面提到的,指针变量本质仍是指针,形参指针位置的改变不会影响实参指针的位置
下面是参考网友的一些总结,红色字体为本人批注:
memcpy实现内存拷贝,根据这个问题,我们可以提取出下面几点:
1.可以拷贝任何数据,数据类型不能受限
2.源数据不能被改变
通过上面两点可以确定函数原型为void *memcpy(void *dest, const void *src),现在分析一下这些足够了吗?这个函数拷贝什么时候结束,当时我就用了这个函数原型,由于是拷贝的任意数据,所以不能指定一个明确的结束标志,既然这样那么只有明确的指定拷贝的大小才可以.所以函数原型变成这样void *memcpy(void *dest, void *src, size_t count);好吧,函数原型既然已经确认了,剩下的应该就是写函数了,先等等,先别急着写函数,实际上对于C语言的开发者来说,重要的不是函数功能的实现,重要的是函数出错时的处理,如果你用的是Java或者C#大不了抛个异常出来,软件崩溃一下,不会对其他造成任何影响;C这东西弄不好会把整个系统弄瘫痪,所谓”兵马未动,粮草先行”,我么还是先考虑考虑出错的问题吧!我们根据函数原型来分析,
void *memcpy(void *dest, const void *src, size_t count);
1.空指针的问题,如果dest、src两者或者两者之一为NULL,那么自然能没得完了;
2.拷贝大小count为小于等于0的值,自然也是不正确的;
3.目标有没有足够的大小容纳源数据,这个我们在函数内部似乎也无法进行保证,但是我们自己也要想到
4.内存地址有没有重叠,这个我们暂时不考虑了。
有了上面的提示写起来自然比较简单了
- #include <stdio.h>
- void *memcpy(void *dest, const void *src, size_t count)
- {
- <span style="white-space:pre"> </span>if (NULL == dest || NULL == src || count <= 0)
- <span style="white-space:pre"> </span>return NULL;
- <span style="white-space:pre"> </span>while (count--)
- <span style="white-space:pre"> </span>*dest++ = *src++;
- <span style="white-space:pre"> </span>return dest;
- }
上面这段代码在Linux中使用gcc编译是没错的,但是会有警告,所以改成这样:
(注意,上述代码我在测试时,不仅有警告还有一个错误:error: invalid use of void expression,这是因为void型的变量或者是函数返回值被使用了。使用下面这段代码是可以通过编译的:)
- <pre name="code" class="cpp">#include <stdio.h>
- void *memcpy(void *dest, const void *src, size_t count)
- {
- if (NULL == dest || NULL == src || count <= 0)
- return NULL;
- while (count--)
- *(char *)dest++ = *(char *)src++;
- return dest;
- }
OK,也就这样了,要是面试官再问起内存重叠的问题,你再和他侃侃.我的面试算是泡汤了.
总结:不要着急慢慢来,根据需求推出原型,根据原型推断问题,这算是个教训吧!!!
补充:
在这里非常感谢博客园的求道于盲 这位好心的网友指出了我程序中的两个错误,再次感谢.
1.返回了一个++过的指针
2.size_t是无符号类型的,size_t的定义为:typedef unsigned int size_t;
所以count<=0,只会判断==0的情况,如果传入-1,会产生一个很大的无符号整型.
希望别人注意,改过的程序如下:
- void *memcpy(void *dest, const void *src, int count)
- {
- <span style="white-space:pre"> </span>void *ptr = dest;
- <span style="white-space:pre"> </span>if (NULL == dest || NULL == src || count <= 0)
- <span style="white-space:pre"> </span>return NULL;
- <span style="white-space:pre"> </span>while (count--)
- <span style="white-space:pre"> </span>*(char *)dest++ = *(char *)src++;
- <span style="white-space:pre"> </span>return ptr;
- }
本文通过汇总一些网上搜集到的资料,总结c语言中的memcpy实现
背景
想必大多数人在面试时被要求写 memcpy的实现,很不幸,我也吃过这个亏(这种题要是写的一塌糊涂后面完全没戏),所以还是得提前准备一下,不然就只能呵呵了。先来看看一段错误的示范: 找茬:)
- void * memcpy(void *dest, const void *src, unsigned int count);
- {
- if ((src == NULL) || (dest == NULL))
- return;
- while (count--)
- *dest++ = *src++;
- return dest;
- }
dest都指到哪里去了?怎么着也得备份一下dest的值,好让函数返回的指针是从头开始的
考虑一下指针类型,如果dest和src的指针类型不一样,不能直接++赋值. 例如: int* p和 char*q, p++指针的值是4个4个加(0,4,8),q++是1个1个加(0,1,2,3,4)
第二版 - 定义两个临时变量,不要直接++ dest和src,并且指明指针类型char *
- void *memcpy(void *dest, const void *src, size_t count)
- {
- char *tmp = dest;
- const char *s = src;
- while (count--)
- *tmp++ = *s++ ;
- return dest;
- }
能否改进? src和dest都强制转换成char*类型的指针,那么copy一定是一个字节一个字节的完成?那么第三版来了
- void * memcpy(void *dst,const void *src,size_t num)
- {
- int nchunks = num/sizeof(dst); /*按CPU位宽拷贝*/
- int slice = num%sizeof(dst); /*剩余的按字节拷贝*/
- unsigned long * s = (unsigned long *)src;
- unsigned long * d = (unsigned long *)dst;
- while(nchunks--)
- *d++ = *s++;
- while (slice--)
- *((char *)d++) =*((char *)s++);
- return dst;
- }
看着没什么问题了,可是如果dst和src地址不对齐,copy效率岂不降低? 是否需要先处理一下地址不对齐的情况?
再来看看dest和src地址有重叠的情况
内存重叠问题是指目的地址的内存空间的首地址,包含在源内存空间中,这两段内存空间有了交集,因而在使用memcpy进行内存复制操作时,这段重叠的内存空间会被破坏.这种情况在应用程序级代码中一般不会出现的,而在驱动或内核级代码中要十分小心,尽量使用memmove函数.
memcpy对内存空间有要求的,dest和src所指向的内存空间不能重叠,否则复制的数据是错误的.下面具体讲解一下这个错误是如何产生的.
如果内存空间布局入下图所示:
src所指向的内存空间后面部分数据被新拷贝的数据给覆盖了(也就是dest<=src+size).所以拷贝到最后,原来的数据肯定不是原来的数据,拷贝的数据也不是想要的数据,使用memcpy函数可以得到错误的结果.
再者,如果内存空间布局入下图所示:
虽然原来的数据不再是原来的数据(dest+size>=src),但拷贝的数据是原来的数据,使用memcpy函数可以得到正确的结果.因此,在使用memcpy这个函数之前,还需要做一个判断,如果dest<=src你才能使用这个函数不过完全没有必要, 解决办法,从高地址向地地址copy
- void *memcpy(void *dest, const void *src, size_t count)
- {
- char *d;
- const char *s;
- if (dest > (src+size)) || (dest < src))
- {
- d = dest;
- s = src;
- while (count--)
- *d++ = *s++;
- }
- else /* overlap */
- {
- d = (char *)(dest + count - 1); /* offset of pointer is from 0 */
- s = (char *)(src + count -1);
- while (count --)
- *d-- = *s--;
- }
- return dest;
- }
•memcpy是把src指向的对象中的size个字符拷贝到dest所指向的对象中,返回指向结果对象的指针.
•memmove也是把src指向的对象中的size个字符拷贝到dest所指向的对象中,返回指向结果对象的指针,但这两个函数在处理内存区域重叠的方式不同.
注意memmove这个函数名称中有"move"这个单词,而实际上src处的数据仍然还在,并没有真的被"移动"了!这个函数名称有它的历史原因,是因为有了memcpy函数后,发现这个函数有问题,又发明了另一个没有问题的memcpy函数,但为了保证兼容性依然保留了memcpy函数,而将新版本的memcpy函数改名为memmove函数.
总结
1. 不要破坏传进来的形参,定义新的临时变量来操作
2.考虑指针的类型,不同类型的指针不能直接++赋值
3.overlap情况下需要从高地址处向前copy
from: http://blog.csdn.net/frank_jb/article/details/51698948
C语言之memcpy函数的更多相关文章
- C语言使用memcpy函数实现两个数间任意位置的复制操作
c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中. 用法:void *memcpy(void *dest ...
- C语言memcpy函数的用法
介绍 memcpy是memory copy的缩写,意为内存复制,在写C语言程序的时候,我们常常会用到它.它的函原型如下: void *memcpy(void *dest, const void *sr ...
- C语言memcpy()函数和memmove()函数
C语言memcpy()函数和memmove()函数 关于 memcpy() 函数,请先看链接. memcpy() 函数和 memmove() 函数的函数原型如下: void* memcpy(void ...
- (C语言)memcpy函数原型的实现
在网上看到一道题,实现一个memcpy函数,于是查了一下memcpy的函数原型,如下: void* memcpy(char *strDest, const char *strSrc, int Coun ...
- memcpy函数的使用方法
c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中. 1.函数原型 void *memcpy(void * ...
- 使用memcpy函数时要注意拷贝数据的长度
memcpy函数简介 memcpy函数是C/C++语言中的一个用于内存复制的函数,声明在 string.h 中(C++是 cstring).其原型是: void *memcpy(void *desti ...
- memcpy函数用法
memcpy函数用法 .分类: VC++ VC++ mfc matlab 2011-12-01 19:17 14538人阅读 评论(0) 收藏 举报 null 原型:extern void *memc ...
- Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针
Atitit java方法引用(Method References) 与c#委托与脚本语言js的函数指针 1.1. java方法引用(Method References) 与c#委托与脚本语言js ...
- 【转】【C/C++】实现memcpy函数
本文转自:http://my.oschina.net/renhc/blog/36345 面试中如问到memcpy的实现,那就要小心了,这里有陷阱. 先看下标准memcpy()的解释: ? 1 2 vo ...
随机推荐
- HTML无刷新提交表单
通常对于无刷新提交表单,我们都是运用ajax实现的.前段时间跟着老大了解到另一种无刷新提交表单的方法,是利用iframe框架实现的.现在整理出来分享给大家. 第一种: (html页面) <!DO ...
- NOIP欢乐模拟赛 T1 解题报告
小澳的方阵 (matrix.cpp/c/pas) [题目描述] 小澳最近迷上了考古,他发现秦始皇的兵马俑布局十分有特点,热爱钻研的小澳打算在电脑上还原这个伟大的布局. 他努力钻研,发现秦始皇布置兵马俑 ...
- 【BZOJ】2795: [Poi2012]A Horrible Poem
题意 一个长度为\(n(n \le 500000)\)的字符串\(s\),给\(q(q \le 2000000)\)个询问,每个询问给一个区间\([l, r]\),求这个区间内最短的循环节. 分析 分 ...
- ios推送:本地通知UILocalNotification
Notification是智能手机应用编程中非常常用的一种传递信息的机制,而且可以非常好的节省资源,不用消耗资源来不停地检查信息状态(Pooling),在iOS下应用分为两种不同的Notificati ...
- BFS 巡逻机器人
巡逻机器人 题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=83498#problem/F 题目大意: 机器人在一个矩形区域巡逻, ...
- 用简单直白的方式讲解A星寻路算法原理
很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高的,因此也成为游戏中 ...
- js写当鼠标悬浮及移开出现背景变化
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- mysql慢日志管理
一.日志切割 原理: 1.cp一个慢日志备份 2.清空原理的慢日志 3.写成脚本,每天一切,这样就ok啦 二.查找日志中的慢日志 1.做了日志切割(慢日志文件就小了) 2.查找某个时间的慢日志 日志时 ...
- iframe自适应高度js
<iframe src="http://www.fufuok.com/" id="iframepage" name="iframepage&qu ...
- 示例说明Oracle RMAN两种库增量备份的差别
1差异增量实验示例 1.1差异增量备份 为了演示增量备份的效果,我们在执行一次0级别的备份后,对数据库进行一些改变. 再执行一次1级别的差异增量备份: 执行完1级别的备份后再次对数据库进行更改: 再执 ...