memcpy函数的实现及改进
memcpy主要是用来拷贝不重叠的内存块。下面一步步的来改进memcpy的实现。
最初的解决方法是使memcpy对NULL指针进行检查,如果指针为NULL,就给出一条错误信息,并中止memcpy的执行。
#include <assert.h>
typedef unsigned char byte;
void *mymemcpy(void *pvTo, void *pvFrom, int size) {
byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
if(NULL == pvTo || NULL == pvFrom) {
fprintf(stderr, "Bad");
abort();
}
while(size-- > 0)
*pbTo++ = *pbFrom++;
return pvTo;
}
该实现代码存在的问题是其中的测试代码使整个函数的大小增加了一倍,并且降低了该函数的执行速度。
如果保存两个版本怎么样?一个整洁快速用于程序的交付;另一个臃肿缓慢件(因为包括额外的检查),用于调试。这样就得同时维护同一程序的两个版本,并利用C的预处理程序有条件地包含或不包含相应的检查部分。
void *mymemcpy(void *pvTo, void *pvFrom, int size) {
byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
#ifdef DEBUG
if(NULL == pvTo || NULL == pvFrom) {
fprintf(stderr, "Bad");
abort();
}
#endif
while(size-- > 0)
*pbTo++ = *pbFrom++;
return pvTo;
}
这种想法是同时维护调试和非调试(即交付)两个版本。在程序的编写过程中,编译其调试版本,利用它提供的测试部分在增加程序功能时自动查错。在程序编写完之后,编译其交付版本,封装之后交给经销商。如果保证调试代码不在最终产品中出现,即既要维护程序的交付版本,又要维护程序的调试版本,可以利用断言来补救。
void *mymemcpy(void *pvTo, void *pvFrom, int size) {
byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
assert(pvTo != NULL && pvFrom != NULL);
while(size-- > 0)
*pbTo++ = *pbFrom++;
return pvTo;
}
assert是个宏。因为要求程序的调试版本和交付版本行为完全相同,所以才不把assert作为函数,而把它作为宏。如果assert作为函数的话,其调用就会引起不期望的内存或代码的兑换。使用assert的程序员是把它看成一个在任何系统状态下都可以安全使用的无害检测手段。程序员可以重定义assert宏。程序员可以把assert定义成当发生错误时不是终止调试程序的执行,而是在发生错误的位置转入调试程序。assert的某些版本甚至可以允许用户选择让程序继续运行,就仿佛从来没有发生过错误一样。除非打算在表达式中使用断言,否则就应该将assert定义为语句。
下面是一种用户自己定义宏ASSERT的方法:
#ifdef DEBUG
void _Assert(char *, unsigned);
#define ASSERT(f) \
if(f) \
NULL; \
else \
_Assert(__FILE__, __LINE__)
#else
#define ASSERT(f) NULL
#endif
void _Assert(char *strfile, unsigned uLine) {
fflush(stdout);
fprintf(stderr, "\nAssertion failed:%s, line %u\n", strfile, uLine);
fflush(stderr);
abort();
}
在abort之前,需要调用fflush将所有的缓冲输出写到标准输出设备stdout上。同样,如果stdout和stderr都指向同一设备,fflush stdout仍然要放在fflush stderr之前,以确保只有在所有的输出都送到stdout之后,fprintf才显示相应的错误信息。
因为memcpy是拷贝的不重叠的内存块,所以还需要增加内存块不重叠的断言。
void *mymemcpy(void *pvTo, void *pvFrom, int size) {
byte *pbTo = (byte *)pvTo;
byte *pbFrom = (byte *)pvFrom;
ASSERT(pvTo != NULL && pvFrom != NULL);
//内存重叠吗?如果重叠,就使用memmove。
assert(pbTo >= pbFrom+size || pbFrom >= pbTo+size);
while(size-- > 0)
*pbTo++ = *pbFrom++;
return pvTo;
}