glibc中strcpy,strcat,strcmp,memcpy等函数的实现和分析

来源:互联网 发布:php开源网盘系统 编辑:程序博客网 时间:2024/06/06 21:45

http://www.oschina.net/code/explore/glibc-2.9/string/memcpy.c 可以找到所有glibc的源代码


strcpy

源码

char *
strcpy (dest, src)
char *dest;
const char *src;
{
    reg_char c;
    char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);//用来检测src是否合法的宏,看地址是否大于最低有效内存地址
    const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;//求出src和dest的距离,用于后面的赋值。注意-1
    size_t n;
 
    do
    {
        c = *s++;//第一次到此处,s == dest, off == dest - s - 1,   
        s[off] = c; //第一次到此处,s == dest + 1 , s[off] == dest; 
                 //此算法利用了进程平坦的内存模型,虚拟内存平坦铺开,于是任意两个指针的差就是二者之间的距离得到地址的相对距离off就不用绝对地址来寻址了,这                 //样在循环中可以少一次dest++操作,而多出来的相对地址定位操作则完全可以用寄存器高效完成
    }
    while (c != '\0');
 
    n = s - src;
    (void) CHECK_BOUNDS_HIGH (src + n);//用来检测src+n是否合法的宏,看地址是否小于最高有效地址
    (void) CHECK_BOUNDS_HIGH (dest + n);
 
    return dest;
}


memcpy

网上对于memcpy的实现讲的有很多,找个比较靠谱的贴到这里,整体代码很漂亮,不过其中用到的DSP优化的while循环展开,其实有点小聪明了,是否可以通过int类型,在32位的CPU上做一个int类的赋值应该比4个字节复制快。

void * memcpy(void * sdt, const void * src, unsigned int len)
{
    register char * d;
    register char * s;
    if (0 == len)
    {
        return dst;
    }
    if (dst > src)
    {
        d = (char *)dst + len - 1;
        s = (char *)src + len - 1;
        while (len >= 4)
        {
            *d-- = *s--;
            *d-- = *s--;
            *d-- = *s--;
            *d-- = *s--;
            len -= 4;
        }
        while (len--)
        {
            *d-- = *s--;
        }
    }
    else if (dst < src)
    {
        d = (char *)dst;
        s = (char *)src;
        while (len >= 4)
        {
            *d++ = *s++;
            *d++ = *s++;
            *d++ = *s++;
            *d++ = *s++;
            len -= 4;
        }
        while (len--)
        {
            *d++ = *s++;
        }
    }
    return dst;
}

  • GNU的实现

#include <string.h>
#include <memcopy.h>
#include <pagecopy.h>
 
#undef memcpy
 
void *
memcpy (dstpp, srcpp, len)
     void *dstpp;
     const void *srcpp;
     size_t len;
{
  unsigned long int dstp = (long int) dstpp;
  unsigned long int srcp = (long int) srcpp;
 
  /* Copy from the beginning to the end.  */
 
  /* If there not too few bytes to copy, use word copy.  */
  if (len >= OP_T_THRES)
    {
      /* Copy just a few bytes to make DSTP aligned.  */
      len -= (-dstp) % OPSIZ;
      BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);
 
      /* Copy whole pages from SRCP to DSTP by virtual address manipulation,
as much as possible.  */
 
      PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);
 
      /* Copy from SRCP to DSTP taking advantage of the known alignment of
DSTP.  Number of bytes remaining is put in the third argument,
i.e. in LEN.  This number may vary from machine to machine.  */
 
      WORD_COPY_FWD (dstp, srcp, len, len);
 
      /* Fall out and copy the tail.  */
    }
 
  /* There are just a few bytes to copy.  Use byte memory operations.  */
  BYTE_COPY_FWD (dstp, srcp, len);
 
  return dstpp;
}
libc_hidden_builtin_def (memcpy)

整个memcpy的流程为:
1. 判断需要拷贝的字节数是否大于某一临界值。如果大于临界值,则可以使用更加强大的优化手段进行拷贝。否则,直接转6。
2. 假设要拷贝的目的地如下所示:

                          glibc memcpy的实现分析(zz) - zhangzhibiao02005 - Technology HERE!
其中start为拷贝目的地的起始地址,end为拷贝目的地的结束地址,align border为内存中的对齐边界。在大多数平台下,从内存对齐边界开始拷贝会有许多的优化方法可以使用,此处memcpy正是利用了这点。
3. 计算start到align border的距离,此处使用了一个非常聪明的小技巧。使用 (-dstp) % OPSIZ 来计算start到align border的距离,这样可以减少一次判断。然后使用字节拷贝的方法来拷贝start到align border之间的内存。
4. 对于特殊平台,可能使用page copy的方法。由于限制条件较多,一般x86平台下不会使用。
5. 使用word copy的方法进行字节块拷贝,此处是memcpy优化的关键,优化的条件是拷贝地址处于对齐边界。在pentium系列平台和非pentium系列平台下,word copy有两种实现方式。
6. 剩余的不能采用word copy的尾部使用字节拷贝。

以下为x86平台下字节拷贝和字节块拷贝的实现
字节拷贝的实现:
#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes)                                      \
do {                                                                              \
    int __d0;                                                                      \
    asm volatile(/* Clear the direction flag, so copying goes forward. */    \
                 "cld\n"                                                      \
                 /* Copy bytes. */                                              \
                 "rep\n"                                                      \
                 "movsb" :                                                      \
                 "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                      \
                 "0" (dst_bp), "1" (src_bp), "2" (nbytes) :                      \
                 "memory");                                                      \
} while (0)
没啥好说的,利用x86的movsb指令实现字节拷贝。使用movsb指令时,需设置EDI,ESI,ECX寄存器的值,EDI寄存器存放拷贝的目的地址,ESI寄存器存放拷贝的源地址,ECX为需要拷贝的字节数。拷贝完成之后,EDI中的值会保存到dst_bp中,ESI中的值会保存到src_bp中。这也是为什么memcpy中没有出现对dst_bp操作的原因。

非Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes)                      \
do                                                                              \
    {                                                                              \
      int __d0;                                                                      \
      asm volatile(/* Clear the direction flag, so copying goes forward. */ \
                   "cld\n"                                                      \
                   /* Copy longwords. */                                      \
                   "rep\n"                                                      \
                   "movsl" :                                                      \
                    "=D" (dst_bp), "=S" (src_bp), "=c" (__d0) :                      \
                   "0" (dst_bp), "1" (src_bp), "2" ((nbytes) / 4) :              \
                   "memory");                                                      \
      (nbytes_left) = (nbytes) % 4;                                              \
    } while (0)
利用x86的movsl指令实现四字节拷贝。如果movsl和movsb花费相同的cpu时钟周期,那优化后的拷贝时间将是原来的四分之一。恩,相当可观了。。。

Pentium平台下的word copy的实现:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes)                \
do                                                                        \
    {                                                                        \
      asm volatile ("subl        $32,%2\n"                                \
                    "js                2f\n"                                        \
                    "movl        0(%0),%%edx\n"        /* alloc dest line */        \
                    "1:\n"                                                \
                    "movl        28(%0),%%eax\n"        /* alloc dest line */        \
                    "subl        $32,%2\n"        /* decr loop count */        \
                    "movl        0(%1),%%eax\n"        /* U pipe */                \
                    "movl        4(%1),%%edx\n"        /* V pipe */                \
                    "movl        %%eax,0(%0)\n"        /* U pipe */                \
                    "movl        %%edx,4(%0)\n"        /* V pipe */                \
                    "movl        8(%1),%%eax\n"                                \
                    "movl        12(%1),%%edx\n"                                \
                    "movl        %%eax,8(%0)\n"                                \
                    "movl        %%edx,12(%0)\n"                                \
                    "movl        16(%1),%%eax\n"                                \
                    "movl        20(%1),%%edx\n"                                \
                    "movl        %%eax,16(%0)\n"                                \
                    "movl        %%edx,20(%0)\n"                                \
                    "movl        24(%1),%%eax\n"                                \
                    "movl        28(%1),%%edx\n"                                \
                    "movl        %%eax,24(%0)\n"                                \
                    "movl        %%edx,28(%0)\n"                                \
                    "leal        32(%1),%1\n"        /* update src ptr */        \
                    "leal        32(%0),%0\n"        /* update dst ptr */        \
                    "jns        1b\n"                                        \
                    "2: addl        $32,%2" :                                \
                    "=r" (dst_bp), "=r" (src_bp), "=r" (nbytes_left) :        \
                    "0" (dst_bp), "1" (src_bp), "2" (nbytes) :                \
                    "ax", "dx");                                        \
    } while (0)
字节块单元的大小变为了32。在执行过程中,利用Pentium平台下的pipeline技术。此处光看代码可能感觉不出来优化,但是联想一下Pentium平台下强大的流水线技术就会发现以上指令中的许多工作都可以并发执行,执行效率会大大提高。


strcmp

int
strcmp (p1, p2)
     const char *p1;
     const char *p2;
{
  register const unsigned char *s1 = (const unsigned char *) p1;
  register const unsigned char *s2 = (const unsigned char *) p2;
  unsigned reg_char c1, c2;


  do
    {
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0')
    return c1 - c2;
    }
  while (c1 == c2);


  return c1 - c2;
}









原创粉丝点击