先用代码给大家简单的模拟下strcpy和memcpy的“原理”。为了方便对指针不熟悉的人阅读,下面都改成了用数组方式表示。
- char* strcpy(char* dest, const char* src);把含有’\0’结束符的字符串复制到另一个地址空间,返回值的类型为char*。
char* myStrcpy(char* dest, const char* src)
{
assert((dest != NULL) && (src != NULL));
int i = 0;
while ((dest[i] = src[i++]) != '\0');
return dest;
}
//char* myStrcpy(char* dest, const char* src)
//{
// assert((dest != NULL) && (src != NULL));
// int i = 0;
// while ((*dest++ = *src++) != '\0');
// return dest;
//}
我们都知道字符串是以’\0’结尾的,其实就是数值0,用’\0’表示的更明确。所以字符串拷贝是遇到’\0’就结束了,不管你char *(内存空间)有多大,够不够放。缺点:1、有时候就会出现内存空间不够大出现操作非法地址的问题,所以就出现了strcpy_s的相对安全的函数,这比strcpy多了个参数,就是确保即使没有遇到’\0’,也能在达到最大拷贝个数时结束拷贝。2、只适用于字符串拷贝,例如变量拷贝、数据拷贝就不适合了。
如果有人看不懂while ((dest[i] = src[i++]) != ‘\0’);文章末尾有对这进行反汇编用C语言的方式去分析他的执行过程。
- void memcpy(void destin, const void* source, unsigned int n);函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
/*
* n代表的是字节数(内存单元个数),不是元素个数或者数组长度。
* 如果不知道数组占用多大的内存空间,那可以用sizeof去求,或者数组长度*sizeof(数组类型)
*/
void *myMemcpy(void* destin, const void* source, unsigned int n)
{
if ((destin == NULL) || (source == NULL))
return NULL;
char* in = (char *)destin;
char* src = (char *)source;
while (n--)
in[n] = src[n];
return in;
}
- main()
int main()
{
char str[10] = "0124";
char strcpyStr[10] = {0};
char memcpyStr[10];
myStrcpy(&str[6], "abc");
myStrcpy(strcpyStr, str);
myMemcpy(memcpyStr, str, sizeof(str));
return 1;
}
调完第一个myStrcpy后,str的数据如下截图,从下标6开始拷贝了"abc"。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL2MDNwATO1ATMyADOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
调完第二个myStrcpy后,strcpyStr的数据如下截图,只拷贝了str的前5个内容,因为第5个内容是’\0’,所以结束了。
调完myMemcpy之后,memcpyStr的数据如下截图,str的10个内容。
结论:strcpy适用于字符串拷贝,而memcpy适用于任何内存拷贝,包括字符串拷贝,只不过要先求字符串长度+1,相对strcpy麻烦些。
题外话
- 反汇编分析while ((dest[i] = src[i++]) != ‘\0’);
地址 | 汇编指令 | 操作数 | C语言 | |
---|---|---|---|---|
– | – | – | extern unsigned long eax; | |
– | – | – | 函数参数:char **dest, const char **src; | |
– | – | – | 临时变量int *i = malloc(sizeof(int)); i = 0; | |
01204130h | mov | eax,dword ptr [dest] | == | eax = *dest; |
01204133h | add | eax,dword ptr [i] | == | eax = eax + *i; |
extern unsigned long ecx; | ||||
01204136h | mov | ecx,dword ptr [src] | == | ecx = *src; |
01204139h | add | ecx,dword ptr [i] | == | ecx = ecx + *i; |
extern unsigned short dl; | ||||
0120413Ch | mov | dl,byte ptr [ecx] | == | dl = *ecx; |
0120413Eh | mov | byte ptr [eax],dl | == | *eax = dl; |
01204140h | mov | eax,dword ptr [dest] | == | eax = *dest; |
01204143h | add | eax,dword ptr [i] | == | eax = eax + *i; |
01204146h | movsx | ecx,byte ptr [eax] | == | ecx = *eax; |
extern unsigned long ebp; | ||||
unsigned long *stack1 = ebp-0D0h; | ||||
01204149h | mov | dword ptr [ebp-0D0h],ecx | == | *stack1 = ecx |
0120414Fh | mov | edx,dword ptr [i] | == | edx = *i; |
01204152h | add | edx,1 | == | edx = edx+1; |
01204155 | mov | dword ptr [i],edx | == | *i = edx; |
bool ZF; | ||||
01204158 | cmp | dword ptr [ebp-0D0h],0 | == | ZF = !(*stack1 - 0); |
0120415F | je | myStrcpy+9Dh (0120416Dh) | == | if(ZF == true) goto 0120416Dh; |
unsigned long stack2 = ebp-0D4h; | ||||
01204161 | mov | dword ptr [ebp-0D4h],1 | == | stack2 = 1; |
0120416B | jmp | myStrcpy+0A7h (01204177h) | == | goto 01204177h; |
0120416D | mov | dword ptr [ebp-0D4h],0 | == | stack2 = 0; |
01204177 | cmp | dword ptr [ebp-0D4h],0 | == | ZF = !(stack2 - 0); |
0120417E | je | myStrcpy+0B2h (01204182h) | == | if(ZF == true) goto 01204182h; |
01204180 | jmp | myStrcpy+60h (01204130h) | == | goto 01204130h; |
eax,ecx,edx,dl,ebp是CPU的寄存器,你可当成是一个全局的unsigned long变量去理解。ZF是标志寄存器上的一位,为1时代表上一步运算结果是0。
从上面的表格可以清楚的知道,dest[i] = src[i++] != ‘\0’是先把src的值取到dest里面,然后再把dest的值取到ecx里,放到* stack1(ebp-0D0h内存地址)上做备用,然后对i的值++,用*stack1去与’\0’比较,如果是0就跳出循环。实际上等同下面的代码
char c;
do{
dest[i] = src[i];
c = dest[i];
++i;
}while(c != '\0');
注:任何的运算都是在寄存器上完成再写回内存地址上的,不能直接在内存地址上做加减法等。