• [读源码]看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.

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

  • 相关阅读:
    .Net中多线程类的使用和总结
    单例模式完整解析
    避免构造/析构函数调用虚函数(转)
    正则表达式
    序列化与反序列化
    数组的使用,指针的使用
    jmeter单接口和多接口测试
    HTML5 input placeholder 颜色修改
    h5动画效果总结
    8月份月度反思--做一个快乐的程序员
  • 原文地址:https://www.cnblogs.com/egmkang/p/1743267.html
Copyright © 2020-2023  润新知