C语言函数strlen源码剖析doc.docx
《C语言函数strlen源码剖析doc.docx》由会员分享,可在线阅读,更多相关《C语言函数strlen源码剖析doc.docx(15页珍藏版)》请在冰豆网上搜索。
C语言函数strlen源码剖析doc
C语言函数strlen源码剖析
发布日期:
2009-04-06来源:
互联网作者:
佚名
strlen源码剖析
学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++RuntimeLibrary)作为底层的函数库,实现必然高效。
恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试。
strlen的函数原形如下:
size_tstrlen(constchar*str);
strlen返冋str屮字符的个数,其屮str为一个以‘\0‘结尾的字符串(anull-terminatedstring)o
1.简单实现
如果不管效率,最简单的实现只需要4行代码:
1size_tstrlen_a(constchar*str){
size_tlength=0;
3while(*str++)
4++length;
5returnlength;
6}
也许可以稍加改进如下:
1size_tstrlen_b(constchar*str){
2constchar*cp=str;
while(*cp++)
4;
5return(cp一str-1);
6}
2.高效实现
很显然,标准库的实现肯定不会如此简单,上面的strlen_a以及strlen_b都是一次判断一个字符直到发现VT为止,这是非常低效的。
比较高效的实现如卞(在这里WORD表示计算机中的一个字,不是WORD类型):
(1)一次判断一个字符直到内存对齐,如果在内存对齐之前就遇到W则直接return,否则到
(2);
(2)一次读入并判断一个WORD,如果此WORD屮没有为0的字节,则继续下一个WORD,否则到(3);
(3)到这里则说明WORD屮至少有一个字节为0,剩下的就是找出第一个为0的字节的位置然后returno
NOTE:
数据对齐(dataalignment).是指数据所在的内存地址必须是该数据长度的整数倍,这样CPU的存取速度最快。
比如在32位的计算机中,一个WORD为4byte,则WORD数据的起始地址能被4整除的时候CPU的存取效率比较高。
CPU的优化规则人概如下:
对于n字节(n=2,4,8...)的元素,它的首地址能被n整除才能获得最好的性能。
为了便于下面的讨论,这里假设所用的计算机为32位,即一个WORD为4个字节。
下面给出在32位计算机上的C语言实现(假设unsignedlong为4个字节):
1typedefunsignedlongulong;
3size_tstrlen_c(constchar*str){
4
4constchar*char_ptr;
5constalong*longword_ptr;
registerulonglongword,magic_bits;
8
9for(char_ptr=str;((ulong)char_ptr
10
&
(sizeof(ulong)一1))
!
=0;
11
++
char_ptr){
12
if
(*char_ptr==1\01)
i
13
returnchar_ptr一
str;
14
}
15
16longword_ptr=(ulong*)char_ptr;
17
17magic_bits=0x7efefeffL;
19
18while
(1){
21
19longword=*longword_ptr++;
23
20
-magic_bits)
if((((longword+magic_bits)八-longword)
26
constchar*cp
=(constchar*)(longword_ptr-1);
27
28
if
(cp[0]:
==0
)
29
return
cp_
str;
30
if
(cp[l):
==0
)
31
return
cp-
str+
1
■f
32
if
(cp[2):
==0
)
33
return
cp-
str+
2
■f
34
if
(cp[3):
==0
)
35
return
cp_
str+
3
■f
36
}
37
}
38}
3.源码剖析
上面给出的C语言实现虽然不算特别复杂,但也值得花点时间來弄清楚,先看9・14行:
for(char_ptr=str;((ulong)char_ptr&(sizeof(ulong)-1))!
=0;++
char_ptr){
if(*char_ptr==1\01)
:
returnchar_ptr一str;
}
上面的代码实现了数据对齐,如果在对齐之前就遇到VT则nJ以直接returnchar_ptr・str;
第16行将longword_ptr指向数据对齐后的首地址
longword_ptr=(along*)char_ptr;
第18行给magic_bits赋值(在丿•面会解释这个值的意义)
magic_bits=0x7efefeffL;
第22行读入一个WORD到longword并将longword_ptr指向下一个WORD
longword=*longword_ptr++;
第24行的if语句是整个算法的核心,该语句判断22行读入的WORD中是否有为0的字节if((((longword+magic_bits)A-longword)&~m4gic_bits)!
=0)
if语句中的计算对以分为如下3步:
(1)longword+magic一bits
其中magic_bits的二进制表示如下:
b3b2blb0
31>0
magic_bits:
01111110111111101111111011111111
magic_bits中的31,24,16,8这些bits都为0,我们把这儿个bits称为holes,注意在每个byte的左边都有一个hole。
检测0字节:
如果longword中有一个字节的所有bit都为0,则进行加法后,从这个字节的右边的字节传递来的进位都会落到这个字节的最低位所在的hole上,而从这个字节的最高位则永远不会产生向左边字节的hole的进位。
则这个字节左边的hole在进行加法后不会改变,由此可以检测出0字节;相反,如果longword中所有字节都不为0,则每个字节中至少有1位为1,进行加法后所有的hole都会被改变。
为了便于理解,请看下面的例子:
b3b2blb0
31>0
longword:
XXXXXXXXXXXXXXXX00000000XXXXXXXX
+magic_bits:
01111110111111101111111011111111
上面longword'P的b1为0,X可能为0也可能为1。
因为b1的所有bit都为0,而从b0传递过来的进位只可能是0或1,很显然b1永远也不会产生进位,所以加法后longword的第16bit这个hole不会变。
(2)ATongword
这一步取出加法后longword中所有未改变的bit。
(3)&~magic_bits
最后取出longword中未改变的hole,如果有任何hole未改变则说明longword中有为0的字节。
根据上面的描述,如果longword中有为0的字节,则if中的表达式结果为非0,否则为0。
NOTE:
如果b3为10000000,则进行加法后第31bit这个hole不会变,这说明我们无法检测出b3
为10000000的所有WORD。
值得庆幸的是用于strlen的字符串都是ASCII标准字符,其值在0-127Z间,这意味着每一个字节的第一个bit都为0。
因此上面的算法是安全的。
一旦检测出longword屮有为0的字节,后面的代码只需要找到第一个为0的字节并返回相应的长度就0K:
constchar*cp=(constchar*)(longword_ptr一1);
if
(cp[0]==0)
returncp一
str;
if
(cp[l]=
==0)
return
cp-
str+
1;
if
(cp[2]=
==0)
return
cp-
str+
2;
if
(cp[3]=
==0)
return
cp-
str+
3;
4.另一种实现
1size_tstrlen_d(constchar*str){
2
constchar*char_ptr;
4constulong*longword_ptr;
5registerulonglongword,himagic,lomagic;
6
for(char_ptr=str;((ulong)char_ptr
8&(sizeof(ulong)-1))!
=0;
9++char_ptr){
10if(*char_ptr==*\0*)
11returnchar_ptr一str;
12}
13
longword_ptr=(ulong*)char_ptr;
himagic=0x80808080L;
lomagic=OxOlOlOlOlL;
while
(1){
longword=*longword_ptr++;
24
25constchar*cp=(constchar*)(longword_pti?
-1);
26
27
if
(cp[0]=
==0)
28
return
cp一str;
29
if
(cp[l]=
==0)
30
return
cp一str+1;
31
if
(cp[2]=
==0)
32
return
cp一str+2;
33
if
(cp[3]:
==0)
34
return
cp一str+3;
35}
36}
37}
上面的代码与strlen_c基本一样,不同的是:
magic_bits换成了himagic和lomagic
himagic=0x80808080L;
lomagic=OxOlOlOlOlL;
以及if语句变得比较简单了
if(((longword一lomagic)&himagic)!
=0)
if语句中的计算可以分为如下2步:
(1)Iongword-lomagic
himagic和lomagic的二进制表示如I、•:
k>3b2blb0
31>0
himagic:
10000000100000001000000010000000
lomagic:
00000001000000010000000100000001
在这种方法中假设所有字符都是ASCII标准字符,其值在0・127之间,因此longword总是如下形式:
b3b2blbO
31>0
longword:
OXXXXXXXOXXXXXXXOXXXXXXXOXXXXXXX
检测0字节:
如果longword屮有一个字节的所有bit都为0,则进行减法后,这个字节的最高位一定会从0变为相反,如果longword中所有字节都不为0,则每个字节中至少有1位为1,进行减法后这个字节的最高位依然为0a
(2)&himagic
这一步取出每个字节最高位的1,如果有任意字节最高位为1则说明longword中有为0的字节。
根据上面的描述,如果longword中有为0的字节,则if中的表达式结果为非0,否则为0。
5.汇编实现
VCCRT的汇编实现与前面strlen_c算法类似
1page,132
2titlestrlen一returnthelengthofanull-terminateds
tring
3•***
4;strlen.asm一containsstrlen()routine
5;
6;Copyright(c)MicrosoftCorporation.Allrightsreserved.
7;
8;Purpose:
9;strlenreturnsthelengthofanull-terminatedstring,
10;notincludingthenullbyteitself.
11;
12;****************************************************************
***************
13
13・xlist
14includecruntime・inc
15.list
17
16page
「9.**★
20;strlen一returnthelengthofanull-terminatedstring
25
28
30
33
35
36
1nullbyte
42
45
47
Findsthelengthinbytesofthegivenstring,notinclud
'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k
★★★★★★★★★★★★★★★
49
50CODESEG
51
51publicstrlen
53
52strlenproc\
53buf:
ptrbyte
56
54OPTIONPROLOGUE:
NONE,EPILOGUE:
NONE
60
61string
equ
[esp+4]
62
63
mov
ecxfstring
;ecx->string
64
test
ecx,3
;testifstringisalign
59
•FPO
(0,1,0,0,0,0)
edon32bits
65
je
shortmain_loop
66
67
str_misaligned:
68
;simple
?
byteloopuntilstring
isaligned
69
mov
al,byteptr[ecx]
70
add
ecx,1
71
test
al,al
72
je
shortbyte_3
73
test
ecx,3
74
jne
shortstr_misaligned
75
76
add
eax,dwordptr0
;5bytenoptoalign丄4b
elbelow
77
78
align
16
■/
shouldberedundant
79
80
main_loop:
81
mov
eaxzdwordptr[ecx]
•f
read
4bytes
82
mov
edx,7efefeffh
83
add
edx,eax
84
xor
eax,-1
85
xor
eax,edx
86
add
ecx,4
87
test
eaxf81010100h
88
je
shortmain_loop
89
;found
zerobyteintheloop
90
mov
eax,[ecx一4]
91
test
alzal
•f
isit
byte0
92
je
shortbyte_0
93
test
ah,ah
•f
isit
byte1
94
je
shortbyte_l
95
test
eax,OOffOOOOh
•f
isit
byte2
96
je
shortbyte_2
97
test
eax,OffOOOOOOh
98
je
shortbyte_3
99
jmp
shortmain_loop
eclearandbit
100
101
102
byte_3:
103
lea
eax,[ecx-1]
104
mov
ecx,string
105
sub
eax,ecx
106
ret
107
byte_2:
108
lea
eax,[ecx一2]
109
mov
ecx,string
110
sub
eaxzecx
111
:
ret
112
byte_l:
113
lea
eax,[ecx一3]
114
mov
ecx,string
115
sub
eax,ecx
116
ret
117
byte_0:
118
lea
eax,[ecx一4]
119
mov
ecx,string
120
sub
eax,ecx
121
ret
122
123
strlenendp
124
125
end
;isitbyte3
takenifbits24-30ar
31isset
6.测试结果
为了对上述各种实现的效率有一个大概的认识,我在VC8和GCC下分别进行了测试,测试时均采用默认优化方式。
下面是在GCC下运行儿百万次后的结果(在VC8下的运行结果与此相似):
strlen_a
1:
515ticks0・515seconds
2:
375
ticks
0.375
seconds
3:
375
ticks
0.375
seconds
4:
375
ticks
0.375
seconds
5:
375
ticks
0.375
seconds
total:
2015
ticks
2.015
seconds
average:
403
ticks
0.403
seconds
strlen_b
1:
360
ticks
0.36
seconds
2:
390
ticks
0.39
seconds
3:
375
ticks
0.375
seconds
4:
360
ticks
0.36
seconds
5:
375
ticks
0.375
seconds
total:
1860
ticks
1・86
seconds
average:
372
ticks
0.372
seconds
strlen_c
1:
187
ticks
0.187
seconds
2:
172
ticks
0.172
seconds
3:
187
ticks
0.187
seconds
4:
187
ticks
0.187
seconds
5:
188
ticks
0.188
seconds
total:
921
ticks
0.921
seconds
average:
184
ticks
0.1842
seconds
strlen_d
1:
172
ticks
0.172
seconds
2:
187
ticks
0.187
seconds
3:
172
ticks
0.172
seconds
4:
187
ticks
0.187
seconds
5:
188
ticks
0.188
seconds
tOtdl:
906
ticks
0.906
seconds
average:
181
ticks
0.1812
seconds
strlen
1:
187
ticks
0.187
seconds
2:
172
ticks
0.172
seconds
3:
188
ticks
0.188
seconds
4:
172
ticks
0.172
seconds
5:
187
ticks
0.187
seconds
total:
906
ticks
0.906
seconds
average:
181
ticks
0.1812
seconds