memcpy和strcpy区别,以及源代码学习

因为自己主要用到得是C语言,所以这里只学习C语言的,C++应该是一样的。

常见用法和区别

头文件

#include

声明

char *strcpy(char* dest,const char* src);
void *memcpy(void*dest, const void *src, size_t n);

两者区别

strcpy只能拷贝字符串。strcpy遇到 '\0'拷贝结束(当dest的内存长度大于src的长度,拷贝时将'\0’带过去,'\0’后面的内容不再拷贝);如果当dest的内存长度小于src的长度,那么会造成内存溢出等问题,所以有了strncpy函数,就是增加了长度控制。

memcpy从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中,可以拷贝任意数据。除了拷贝字符串外,还能拷贝其他的类型的数据,比如结构体,整型数据,类等。memcpy拷贝时需要带有长度参数。

Linux man手册关于二者的描述

strcpy

    The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest. The strings may not overlap, and the destination string dest must be large enough to receive the copy. Beware of buffer overruns!  (See BUGS.)
    The strncpy() function is similar, except that at most n bytes of src are copied. Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.
    If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.

中文翻译:

strcpy()函数将src指向的字符串,包括终止null字节('\0')复制到dest指向的缓冲区,字符串不能重叠,目标字符串dest必须足够大才能接收到拷贝。小心缓冲区溢出!(见缺陷)

strncpy()函数与此类似,只是最多复制了n个字节的src。警告:如果src的前n个字节中没有null字节,放置在dest中的字符串将不会以null结尾。如果src的长度小于n, strncpy()将额外的空字节写入dest,以确保总共写入了n个字节。

memcpy

The  memcpy() function copies n bytes from memory area src to memory area dest.  The memory areas must not overlap.  Use memmove(3) if the memory areas do overlap.

中文翻译:

memcpy()函数从内存区域src复制n个字节到内存区域dest。内存区域不能重叠。如果内存区域确实重叠,则使用memmove(3)。

两个函数的例子这里就不写了,网上有很多。

二者的效率

网上有很多strcpy和memcpy的源代码都是差不多的,都是一个字节一个字节的拷贝的,那memcpy(指定长度全拷贝)的效率岂不低于strcpy(遇到'\0'就结束)?和我们的了解有悖啊。

这里说下个人观点,C语言经过几十年的发展,标准库函数是经过一代又一代的大佬们优化过的,如果memcpy和strcpy差不多,不合常理,怕也会是个笑话。所以个人认为那都是网上贴的自己实现的memcpy函数,可以理解为my_memcpy函数。这里先说下结论,memcpy的效率绝大多数要高于strcpy,具体的看后面的源码分析。

总结

memcpy和strcpy都可以拷贝字符串。但memcpy可以拷贝任意类型,strcpy只能拷贝字符串,且在拷贝字符串的时候要清楚'\0'是否拷贝进去了,是否会发生溢出等,建议使用strncpy。

效率:绝大多数情况下memcpy的效率要高于strcpy。

安全:memcpy因为有长度控制,相对来说更安全。但有内存重叠的风险。

思考1:既然memcpy完胜strcpy,那么strcpy存在的意义是什么?这个需要继续学习。

源代码学习

strcpy源代码

char * __cdecl strcpy(char * dst, const char * src)
{
        char * cp = dst;
 
        while( *cp++ = *src++ ); /* Copy src over dst */
 
        return( dst );
}

很简单,也很精炼,就是一个字节一个字节的复制。

 

memcpy源代码

void *memcpy (void *dstpp, const void *srcpp, size_t len )
{
  unsigned long int dstp = (long int) dstpp;
  unsigned long int srcp = (long int) srcpp;
 
  if (len >= OP_T_THRES)
    {
      len -= (-dstp) % OPSIZ;
      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
      WORD_COPY_FWD (dstp, srcp, len, len);
    }
 
  BYTE_COPY_FWD (dstp, srcp, len);
 
  return dstpp;
}

源码的具体分析看文末的参考链接,这里只写一下结论:

1.对于特殊平台,可以使用page copy的方法。由于限制条件太多,一般x86平台下不会使用。

2.使用word copy的方法进行one word by one word进行拷贝,此处是memcpy的优化关键,优化的条件是拷贝地址处于对齐边界。

3.剩余的不能采用word copy的尾部使用one byte by one byte进行拷贝。


如上,memcpy 好点儿的平台可以优化到按页拷贝,一般的也是one word by one word按照机器字长(32位设备4字节,64位设备8字节)拷贝。而strcpy只能按照one byte by one byt单字节字节拷贝,所以这个效率就不用说了,自然是memcpy的效率更高。

前面说过绝大多数情况下memcpy的效率比strcpy高,这里说的是绝大多数,也就是说还有strcpy效率高于memcpy的情况?

答案是有的,我们不妨假设一个极端情况。src内容的长度只有1个字符,需要拷贝的内容长度是1000个字节。请注意看strcpy循环结束的条件src == ’\0‘,所以strcpy只循环了两次就完成了拷贝,而memcpy是按照长度拷贝的,所以不管src后面有没有内容,依然拷贝1000个字节的内存,所以这种情况下,strcpy的效率是高于memcpy的。

这里贴下strncpy的源代码;

/* Copyright (C) 1991, 1997, 2003 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
 
   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
 
   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
 
   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   .  */
 
#include 
#include 
 
#undef strncpy
 
#ifndef STRNCPY
#define STRNCPY strncpy
#endif
 
char *
STRNCPY (char *s1, const char *s2, size_t n)
{
  char c;
  char *s = s1;
 
  --s1;
 
  if (n >= 4)
	{
		size_t n4 = n >> 2;

		for (;;)
		{
			c = *s2++;
			*++s1 = c;
			if (c == '\0')
				break;
			c = *s2++;
			*++s1 = c;
			if (c == '\0')
				break;
			c = *s2++;
			*++s1 = c;
			if (c == '\0')
				break;
			c = *s2++;
			*++s1 = c;
			if (c == '\0')
				break;
			if (--n4 == 0)
				goto last_chars;
		}
		n = n - (s1 - s) - 1;
		
		if (n == 0)
			return s;
		goto zero_fill;
	}
 
last_chars:
  n &= 3;
  if (n == 0)
    return s;
 
  do
	{
		c = *s2++;
		*++s1 = c;
		if (--n == 0)
	return s;
	}
  while (c != '\0');
 
zero_fill:
  do
    *++s1 = '\0';
  while (--n > 0);
 
  return s;
}
libc_hidden_builtin_def (strncpy)

分析strncpy的源码,关键代码

zero_fill:
  do
    *++s1 = '\0';
  while (--n > 0);

  只有当n等于0时,才返回dest地址,否则即使src后面的数据为空('\0'),依然在dest后面的内存中置0,直到复制完n个字符为止。所以可以看到strncpy增加了长度参数提升字符拷贝的安全性,但效率却降低了,尽管这里的算法已经优化,但是后面置0的操作牺牲了效率和性能。

参考:

《大并发服务器内存转换的灵活运用,memcpy的思考》

《glibc--memcpy源码分析》

《简说GLIBC strncpy实现》

 

 

你可能感兴趣的:(C语言)