zoukankan      html  css  js  c++  java
  • [读源码]看libc里面实现的strcpy

    [读源码]看libc里面实现的strcpy

    这个libc选用的是dietlibc-0.32,是一位老大介绍的,说是比较简单供学习只用.看后大呼上当....(代码写的个人觉得很糟糕)

    为什么说这个strcpy呢?略微有那么一点理由:
    1. 考试几乎必考.而考试给的标准答案很怪异....
    2. 这个strcpy有提供了两个实现,第二个实现的效率比VC9的汇编实现的strcpy效率还要高,我粗略的测算,要高30%.
        这在很大程度上获得了我的注意力:-D(NND,哪个程序员不希望自己的程序跑的贼快)
    3. 也看看别人的代码是怎么写出来的,学习一下

    说明一下,这个libc代码的来源貌似比较复杂,有来自BSD的,有来自GNU,还有一些其他的....(大杂烩%>_<%)
    OK,上代码:

    #define UNALIGNED(x,y) (((unsigned long)x & (sizeof (unsigned long)-1)) ^ ((unsigned long)y & (sizeof (unsigned long)-1)))
    #define STRALIGN(x) (((unsigned long)x&3)?4-((unsigned long)x&3):0)
    # define MKW(x) (x|x<<8|x<<16|x<<24)
    # define GFC(x) ((x)&0xff)
    # define INCSTR(x) do { x >>= 8; } while (0);
    
    char *
    strcpy (char *s1, const char *s2)
    {
        char           *res = s1;
    #ifdef WANT_SMALL_STRING_ROUTINES
        while ((*s1++ = *s2++));
        return (res);
    #else
        int             tmp;
        unsigned long   l;
    
        if (UNALIGNED(s1, s2)) {
        while ((*s1++ = *s2++));
        return (res);
        }
        if ((tmp = STRALIGN(s1))) {
        while (tmp-- && (*s1++ = *s2++));
        if (tmp != -1) return (res);
        }
    
        while (1) {
        l = *(const unsigned long *) s2;
        if (((l - MKW(0x1ul)) & ~l) & MKW(0x80ul)) {
            while ((*s1++ = GFC(l))) INCSTR(l);
            return (res);
        }
        *(unsigned long *) s1 = l;
        s2 += sizeof(unsigned long);
        s1 += sizeof(unsigned long);
        }
    #endif
    }
    

    那些需要的宏我也给拎出来了,好看一些.
    先来看这个简单的实现:WANT_SMALL_STRING_ROUTINES
        while ((*s1++ = *s2++));
        return (res);
    就两行代码,也确实够简单的....这个不说.

    再看后面的实现,前面两个宏的判断我也不想看,貌似是什么是否是四字节对齐的判断,猜的...
    真正有意思的在这里while(1)这里,这里才是问题的关键.
    DEBUG几次大约就能知道if语句里面判断字符串是否包含'\0'.
    如果包含'\0',那么就用while把剩余的字符串拷贝过去;
    如果不包含'\0',那么就用long拷贝,因为一次可以拷贝四个字节.
    if语句外面的,基本上大家都知道意思了,问题就在里面的,里面猜是能猜到拷贝剩余的(不足四个字节的)字符串.
    先来看:
            while ((*s1++ = GFC(l))) INCSTR(l);
            return (res);
    这两句代码吧.
    while( *si++ = GetFirstChar(l) )      //获取l的第一个字符
        INC_STR(l);                        //往后偏移一个字节

    那么他是怎么判断这个long里面是否有'\0'的呢?问题的关键就在
        ((l - MKW(0x1ul)) & ~l) & MKW(0x80ul)
    这句代码上!
    这句代码看这个很费解,我看了很长时间才看懂了.OK,提个问题,你怎么判断一个字节是否是0呢?
    你也许会用==0,可是这样的代码只能对一个字节有效,这个libc用了一种比较复杂的办法:
    byte i;
    ((i - 0x1) & ~i) & 0x80
    他是这么判断的.
    那句代码可以这么写(i-1) & (0xFF-i) & 0x80,我们画一个表就明白是怎么回事了.

    i          0          1         2       ....    127         128         129       ....    253         254        255    
    i-1       255        0         1       ....    126         127         128       ....    252         253        254
    255-i     255        254        253     ....    128         127         126       ....    2           1         0
    &          255        <=0        <=1    ....    <=126    <=127    <=126    ....    <=2       <=1      <=0

    有一个命题,N >= M,那么,(N & M) <= M 必然成立.(谁帮忙证明一下^_^)
    那么(i-1) & (255-i)里面最大的数,也就指望中间这就几个数了,很可惜
        127 & 127 = 127 < 128 = 0x80
    所以他就用这个算法,去衡量一个byte是否是'\0',也就是0.
    是0的话,会返回255;否则返回0.
    一个long里面有四个byte,只要有一个byte出现0,也就是出现字符串的终结符,都会是那个表达式变成一个非0的数字,从而他的目的达到了.

    OK,再来回顾一下他是怎么做的呢?
    1. 读取四个字节,构成一个long
    2. 判断这个long里面有没有包含C String的终结符'\0'
    3. 包含的话,按byte拷贝
    4. 不包含的话,按long拷贝
    5. 回到1


    挺犀利的,但是昨天晚上睡觉想到了一个问题,这个有问题.
    1. 我看过FreeBSD的strcpy,在FreeBSD的libkern里面实现的那个
    2. VC的strcpy是汇编实现的,MS在有可能的情况下,可能会极限优化程序
    他们为什么都不用这个算法呢??
    先来看看FreeBSD里面怎么实现strcpy的:

    char *
    strcpy(char * __restrict to, const char * __restrict from)
    {
        char *save = to;
    
        for (; (*to = *from); ++from, ++to);
        return(save);
    }
    

    这段代码和MS的strcpy虽然很不同,但是效率没多少差别,至少不会差别30%,也就是说MS的汇编,跟FreeBSD的算法上面是一样的.
    他们都选择读取一个字节的!
    刚才说那个libc实现的strcpy有问题,问题在哪里呢??
    他一次读四个字节,可能字符串所占的空间不可能老是四的倍数,所以,他这个会越界!!!有可能会是你的程序Down掉....
    验证一下:
    char src[4]={1,0,1,1};
    char dest[10]={0};
    strcpy(dest,src);
    然后你下一个断点试一试,看看long的值是不是0x01010001.
    所以,看着这个算法的效率很高,实际是不可靠的.

    PS:
    1. dietlibc-0.32的string.h里面,有好几个函数都是用这个算法实现
    2. dietlibc-0.32的代码风格很糟糕,跟FreeBSD的相比,s1,s2这样的命名...有时候很难看懂%>_<%
    3. 底层库,效率固然重要,但是正确是前提
    4. 另外问一个问题,我new char[1]和malloc(1)会给我分配四个字节么?标准应该没有类似的说法吧!

    PS:

    回去看了一下C99的文档,文档里面这么描述:

    The malloc function allocates space for an object whose size is specified by size and whose value is indeterminate.

    所以,我打算维持我之前所做的判断,这个有可能会越界,因为他所做的假设,不一定完全成立.

  • 相关阅读:
    Eclipse安装Hadoop插件
    (转)Ubuntu14.0.4中hadoop2.4.0伪分布模式配置
    Hadoop--DataNode无法启动
    启动与关闭hadoop
    hadoop中执行命令时发生错误
    strings命令
    Deriving data from ElasticSearch Engine
    elasticsearch data importing
    reading words in your computer and changing to female voice, linux festival text2wave saving wav files
    DDNS client on a Linux machine
  • 原文地址:https://www.cnblogs.com/egmkang/p/1743267.html
Copyright © 2011-2022 走看看