您的位置:首页 > 其它

glibc2.7源码分析之strlen()函数

2009-08-19 12:53 399 查看
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

1)能不能保证测试出所有的0bytes。

假设有一个全零的字节。任何来自其左侧的进位都会调进“洞”中,并停止

向高位继续传播。这样,这个全零字节就没有向高位字节的进位,那么,结

果中,对应的左侧的字节的最底位就不会改变。0就会被检测出来。

2)是否会忽略所有的字节,除了全零字节。

假设LONGWORD的每一个字节都有一个非零的位。有一个进位会进到

8位。如果8位非零,进位会传递到16位。如果8位是零,9到15位中,

有一个不是零,因此16位仍然有进位。同样,有进位到24位。如果24

到30位有不是零的,31位就会有进位,所以,所有的洞都会改变。

失败发生在LONGWORD的24到30位是0而31位是1.在这种情况下,31位

的洞不会改变。如果我们可以操作处理器的进位标记,我们可以

通过吧第四个洞放在32位来关闭这个枪眼。

So it ignores everything except 128's, when they're aligned

properly.

*/

longword = *longword_ptr++;

if (

#if 0

/* Add MAGIC_BITS to LONGWORD. */

(((longword + magic_bits)

/*

* 通过这个异或,可以把得到的和中洞的对应位进行设置。

* 如果没有从低位向“洞”的进位,那么异或后其值仍然是零;

* 如果没有进位,则其值为1.

* 这样,就可以知道后面的字节是否有全零的。

* (没有进位时,后面的字节为全零)

*/

^ ~longword)

/*

* 只看“洞”。如果任何一个“洞”的值发生改变,那么极有可能某个

* 字节为全零。

*/

& ~magic_bits)

#else

((longword - lomagic) & himagic)

#endif

!= 0)

{

/*

* Which of the bytes was the zero? If none of them were, it was

* a misfire; continue the search.

*/

const char *cp = (const char *) (longword_ptr - 1);

if (cp[0] == 0)

return cp - str;

if (cp[1] == 0)

return cp - str + 1;

if (cp[2] == 0)

return cp - str + 2;

if (cp[3] == 0)

return cp - str + 3;

/*处理64位的情况,也即每8个字符一组的比较情况*/

if (sizeof (longword) > 4)

{

if (cp[4] == 0)

return cp - str + 4;

if (cp[5] == 0)

return cp - str + 5;

if (cp[6] == 0)

return cp - str + 6;

if (cp[7] == 0)

return cp - str + 7;

}

}

}

}

libc_hidden_builtin_def (strlen)

由代码的第122行的条件编译可以看出来,真正进行测试的时候,使用的是((longword - lomagic) & himagic)。

很明显,这个要比前一个运行的快。这个的原理如下:
如果有一个字节为全零,在进行减法的时候,这个字节会向高位字节借位,这样,这个字节的最高位就变成了1。
对于asc码,每个字节的最高位都是0,这样就可以检测出是否有全零的字节。
但是对于汉字,上面的算法就很低效了。汉字在转换成long int后,高位都是1,这样每次都要一个字节一个字节的测试。
因此,用strlen函数计算汉字字符串,其速度要慢的多。
以上是笔者的一点拙见,如有错误,还请读者批评指出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: