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 ([email protected]),
  4     with help from Dan Sahlin ([email protected]);
  5     commentary by Jim Blandy ([email protected]).
  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函数计算汉字字符串,其速度要慢的多。
以上是笔者的一点拙见,如有错误,还请读者批评指出。



你可能感兴趣的:(源码分析)