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函数计算汉字字符串,其速度要慢的多。
以上是笔者的一点拙见,如有错误,还请读者批评指出。
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函数计算汉字字符串,其速度要慢的多。
以上是笔者的一点拙见,如有错误,还请读者批评指出。
相关文章推荐
- glibc源码分析之stat64系列函数
- glibc源码分析之chmod系列函数
- glibc源码分析之chown系列函数
- glibc源码分析之stat系列函数
- glibc函数exit源码分析
- strlen glibc 源码分析
- glibc源码分析之utime系列函数
- glibc -- strlen源码分析
- glibc源码分析之statfs系列函数
- glibc源码分析之truncate系列函数
- Linux下库函数动态链接过程分析-结合glibc-2.11源码
- Linux-0.11内核源码分析系列:内存管理free_page()与free_page_tables()函数分析
- Uboot-1.1.2 do_bootm_linux函数源码分析
- glibc源码分析之getpriority,setpriority,nice
- netfilter源码分析(5)- ipt_do_table()函数,数据包的过滤
- Linux-0.11内核源码分析系列:内存管理get_free_page()函数分析
- Dubbo2.7源码分析-Dubbo是如何整合spring-framework的
- Dubbo2.7源码分析-SPI的应用
- Linux内核源码分析_setup_arch函数的主要作用
- android 动态注册JNI函数过程源码分析