天天看点

C语言strcpy和memcpy

先用代码给大家简单的模拟下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"。

C语言strcpy和memcpy

调完第二个myStrcpy后,strcpyStr的数据如下截图,只拷贝了str的前5个内容,因为第5个内容是’\0’,所以结束了。

C语言strcpy和memcpy

调完myMemcpy之后,memcpyStr的数据如下截图,str的10个内容。

C语言strcpy和memcpy

结论: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');
           

注:任何的运算都是在寄存器上完成再写回内存地址上的,不能直接在内存地址上做加减法等。