zoukankan      html  css  js  c++  java
  • glibc2.7源码分析之strlen()函数

    glibc2.7中的strlen函数没有使用简单的逐位测试null的方法计算字符串的长度,而是通过一个magic number,每四位一组测试一次的方法进行测试。
    magic number为:0x7efefeff。二进制展开后为:01111110 11111110 11111110 11111111
    其实就是一个第8,16,14,31位为0,其余都是1的整数。这些为0的位称为“洞(hole)”。
    首先将连续的四个字符转换成一个长整型(long int),然后通过magic number测试四个字节中是否有个一字节是全零的。
    测试的运算式如下:
              (((longword + magic_bits)^ ~longword)& ~magic_bits)
    其中,longword就是四个连续的字符转换而来的长整数。magic_bits为那个magic number。
    其实就是测试各个"洞"在进行加法的时候,是否有从其右侧来的进位。如果有进位,则右侧的这个字节就不可能为零。如果没有进位,则右侧这个字节就
    极有可能是0,也就是说,这个字节所表示的字符可能是'\0'(null)。
    异或~longword是为了将在“洞”发生的加法运算的运算结果消除,只留下进位的影响,这样就可以判断是否有进位到"洞"。
    与~magci_bits就是测试各个"洞",是否有发生变化的。

    代码如下:(注释已经翻译成汉语。水平有限,大家凑合着看。。。^-^)
      1 /* Copyright (C) 1991, 1993, 1997, 2000, 2003 Free Software Foundation, Inc.
      2    This file is part of the GNU C Library.
      3    Written by Torbjorn Granlund (tege@sics.se),
      4    with help from Dan Sahlin (dan@sics.se);
      5    commentary by Jim Blandy (jimb@ai.mit.edu).
      6 
      7    The GNU C Library is free software; you can redistribute it and/or
      8    modify it under the terms of the GNU Lesser General Public
      9    License as published by the Free Software Foundation; either
     10    version 2.1 of the License, or (at your option) any later version.
     11 
     12    The GNU C Library is distributed in the hope that it will be useful,
     13    but WITHOUT ANY WARRANTY; without even the implied warranty of
     14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15    Lesser General Public License for more details.
     16 
     17    You should have received a copy of the GNU Lesser General Public
     18    License along with the GNU C Library; if not, write to the Free
     19    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     20    02111-1307 USA.  
     21 */
     22 
     23 #include <string.h>
     24 #include <stdlib.h>
     25 
     26 #undef strlen
     27 
     28 /* 
     29  * 快速的搜索null结束标志。
     30  * 主要思想:
     31  *         每4个字符一组(或8个,取决于运行平台的字长)进行判断。
     32  *         判断'\0'的位置。
     33  *         可以将速度比一个一个比较提高将近四倍。
     34  *         只要是magic number的使用和意义。
     35  *         
     36  */
     37 
     38 size_t strlen (str)
     39     const char *str;
     40 {
     41       const char *char_ptr;
     42       const unsigned long int *longword_ptr;
     43       unsigned long int longword, magic_bits, himagic, lomagic;
     44 
     45       /* 
     46      * 由于后面是将四个连着的字符转换成long int,因此需要进行对齐。
     47      * 这个循环用于对齐。
     48         */
     49       for (     char_ptr = str; 
     50             ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0;
     51                ++char_ptr
     52         )
     53       {
     54         if (*char_ptr == '\0')
     55               return char_ptr - str;
     56     }
     57 
     58       /*  
     59      * 所有的概念设计都是针对4字节长的长字(双字),
     60      * 但对于8字节的双字同样适用。
     61      */
     62 
     63       longword_ptr = (unsigned long int *) char_ptr;
     64 
     65       /* 
     66          * bits:  01111110 11111110 11111110 11111111
     67          * bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
     68      * 魔法数字:
     69      *     第31、24、16和8位为0,其余为1.这些为零的数字被称为“洞”。每个字节的左面
     70      *     都有一个洞(前一个字节的最右面。)。
     71      * 为1的位保证进位被传递到下一个0位中。
     72      * 为0的位用来将进位放入其中。
     73      */
     74       magic_bits = 0x7efefeffL;
     75       himagic = 0x80808080L;
     76       lomagic = 0x01010101L;
     77 
     78       if (sizeof (longword) > 4)
     79     {
     80     /* 64位的情况,分两次位移16位,是为了防止当long只有32位时出现警告。 */
     81           magic_bits = ((0x7efefefeL << 16<< 16| 0xfefefeffL;
     82           himagic = ((himagic << 16<< 16| himagic;
     83           lomagic = ((lomagic << 16<< 16| lomagic;
     84     }
     85 
     86     /* 高于64位是,程序出错! */
     87       if (sizeof (longword) > 8)
     88         abort ();
     89 
     90       /* 
     91      * 通过每次测试一个长字(四个字符),替代传统的每次测试一个字符。
     92      * 当这四个字符中任一个为null时,依次测试这四个字符。
     93      */
     94       for (;;)
     95     {
     96         /* 
     97         当把MAGIC_BITS加到LONGWORD时,没有改变LONGWORD中的任何一个hole,
     98         这时,我们退出循环。
     99 
    100         1)能不能保证测试出所有的0bytes。
    101         假设有一个全零的字节。任何来自其左侧的进位都会调进“洞”中,并停止
    102         向高位继续传播。这样,这个全零字节就没有向高位字节的进位,那么,结
    103         果中,对应的左侧的字节的最底位就不会改变。0就会被检测出来。
    104 
    105         2)是否会忽略所有的字节,除了全零字节。
    106         假设LONGWORD的每一个字节都有一个非零的位。有一个进位会进到
    107         8位。如果8位非零,进位会传递到16位。如果8位是零,9到15位中,
    108         有一个不是零,因此16位仍然有进位。同样,有进位到24位。如果24
    109         到30位有不是零的,31位就会有进位,所以,所有的洞都会改变。
    110 
    111         失败发生在LONGWORD的24到30位是0而31位是1.在这种情况下,31位
    112         的洞不会改变。如果我们可以操作处理器的进位标记,我们可以
    113         通过吧第四个洞放在32位来关闭这个枪眼。
    114 
    115          So it ignores everything except 128's, when they're aligned
    116          properly.  
    117        */
    118 
    119           longword = *longword_ptr++;
    120 
    121           if (
    122 #if 0
    123               /* Add MAGIC_BITS to LONGWORD.  */
    124               (((longword + magic_bits)
    125 
    126             /*
    127              * 通过这个异或,可以把得到的和中洞的对应位进行设置。
    128              * 如果没有从低位向“洞”的进位,那么异或后其值仍然是零;
    129              * 如果没有进位,则其值为1.
    130              * 这样,就可以知道后面的字节是否有全零的。
    131              * (没有进位时,后面的字节为全零)
    132              */ 
    133             ^ ~longword)
    134 
    135                /* 
    136              * 只看“洞”。如果任何一个“洞”的值发生改变,那么极有可能某个
    137              * 字节为全零。
    138              */
    139                & ~magic_bits)
    140 #else
    141               ((longword - lomagic) & himagic)
    142 #endif
    143               != 0)
    144           {
    145               /* 
    146               * Which of the bytes was the zero?  If none of them were, it was
    147               * a misfire; continue the search.  
    148               */
    149 
    150               const char *cp = (const char *) (longword_ptr - 1);
    151 
    152               if (cp[0== 0)
    153                 return cp - str;
    154               if (cp[1== 0)
    155                 return cp - str + 1;
    156               if (cp[2== 0)
    157                 return cp - str + 2;
    158               if (cp[3== 0)
    159                 return cp - str + 3;
    160 
    161               /*处理64位的情况,也即每8个字符一组的比较情况*/
    162             if (sizeof (longword) > 4)
    163             {
    164                   if (cp[4== 0)
    165                     return cp - str + 4;
    166                   if (cp[5== 0)
    167                     return cp - str + 5;
    168                   if (cp[6== 0)
    169                     return cp - str + 6;
    170                   if (cp[7== 0)
    171                     return cp - str + 7;
    172             }
    173         }
    174     }
    175 }
    176 libc_hidden_builtin_def (strlen)
    由代码的第122行的条件编译可以看出来,真正进行测试的时候,使用的是((longword - lomagic) & himagic)。
    很明显,这个要比前一个运行的快。这个的原理如下:
    如果有一个字节为全零,在进行减法的时候,这个字节会向高位字节借位,这样,这个字节的最高位就变成了1。
    对于asc码,每个字节的最高位都是0,这样就可以检测出是否有全零的字节。
    但是对于汉字,上面的算法就很低效了。汉字在转换成long int后,高位都是1,这样每次都要一个字节一个字节的测试。
    因此,用strlen函数计算汉字字符串,其速度要慢的多。
    以上是笔者的一点拙见,如有错误,还请读者批评指出。



  • 相关阅读:
    jquery多次上传同一张图片
    选中没有选中的复选框,匹配含有某个字符串的正则,json取值的两种方法,把变量定义在外面跟里面的区别
    点击div全选中再点击取消全选div里面的文字
    this指向问题
    js的querySelector跟querySelectorAll
    点击下箭头⤵️变上箭头⬆️来回切换的两种方法
    a标签的href值
    padding下中英文左右两端对齐
    map()函数
    设计模式
  • 原文地址:https://www.cnblogs.com/kernel_hcy/p/1549709.html
Copyright © 2011-2022 走看看