1、C语言函数strlen源码剖析docC语言函数strlen源码剖析发布日期:2009-04-06来源:互联网 作者:佚名strlen源码剖析学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C+ Runtime Library)作为 底层的函数库,实现必然高效。恰好手中就有glibc和VC的CRT源代码,于是挑了一个相 对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试。strlen的函数原形如下:size_t strlen (const char *str);strlen返冋str屮字符的个数,其屮str为一个以0结尾的字符串(a null-terminated
2、string)o1. 简单实现如果不管效率,最简单的实现只需要4行代码:1 size_t strlen_a(const char * str) size_t length = 0 ;3 while (*str+ )4 + length;5 return length;6 也许可以稍加改进如下:1 size_t strlen_b(const char * str) 2 const char *cp = str;while (*cp+ )4 ;5 return (cp 一 str - 1 );6 2. 高效实现很显然,标准库的实现肯定不会如此简单,上面的strlen_a以及strlen_b都是一次
3、判断一 个字符直到发现VT为止,这是非常低效的。比较高效的实现如卞(在这里WORD表示计算 机中的一个字,不是WORD类型):(1) 一次判断一个字符直到内存对齐,如果在内存对齐之前就遇到W则直接return,否则到(2) ;(2) 一次读入并判断一个WORD,如果此WORD屮没有为0的字节,则继续下一个WORD, 否则到(3);(3) 到这里则说明WORD屮至少有一个字节为0,剩下的就是找出第一个为0的字节的位 置然后return oNOTE:数据对齐(data alignment).是指数据所在的内存地址必须是该数据长度的整数倍,这样CPU 的存取速度最快。比如在32位的计算机中,一个WO
4、RD为4 byte,则WORD数据的起始 地址能被4整除的时候CPU的存取效率比较高。CPU的优化规则人概如下:对于n字节(n =2,4,8.)的元素,它的首地址能被n整除才能获得最好的性能。为了便于下面的讨论,这里假设所用的计算机为32位,即一个WORD为4个字节。下面 给出在32位计算机上的C语言实现(假设unsigned long为4个字节):1 typedef unsigned long ulong;3 size_t strlen_c (const char * str) 44 const char * char_ptr;5 const along * longword_ptr;reg
5、ister ulong longword, magic_bits;89 for (char_ptr = str; (ulong)char_ptr10&(sizeof (ulong) 一 1)!= 0 ;11+char_p tr) 12if(*char_ptr = 101 )i13return char_ptr 一str;141516 longword_ptr = (ulong* )char_ptr;1717 magic_bits = 0x7efefeffL ;1918 while (1 ) 2119 longword = *longword_ptr+ ;2320 -magic_bits)if
6、( ( ( (longword + magic_bits) 八 -longword)26const char *cp= (const char*) (longword_ptr - 1 );2728if(cp0:=0)29returncp _str;30if(cpl):=0)31returncp -str +1 f32if(cp2):=0)33returncp -str +2 f34if(cp3):=0)35returncp _str +3 f363738 3.源码剖析上面给出的C语言实现虽然不算特别复杂,但也值得花点时间來弄清楚,先看914行:for (char_ptr = str; ( (u
7、long) char_ptr & (sizeof (ulong) - 1) ) ! = 0; +char_ptr) if (*char_ptr = 101):return char_ptr 一 str;上面的代码实现了数据对齐,如果在对齐之前就遇到VT则nJ以直接return char_ptrstr;第16行将longword_ptr指向数据对齐后的首地址longword_ptr = (along*)char_ptr;第18行给magic_bits赋值(在丿面会解释这个值的意义)magic_bits = 0x7efefeffL;第22行读入一个WORD到longword并将longword_p
8、tr指向下一个WORDlongword = *longword_ptr+;第24行的if语句是整个算法的核心,该语句判断22行读入的WORD中是否有为0的字节 if (longword + magic_bits) A -longword) & m4gic_bits) != 0)if语句中的计算对以分为如下3步:(1) longword + magic一bits其中magic_bits的二进制表示如下:b3 b2 bl b031 0magic_bits: 01111110 11111110 11111110 11111111magic_bits中的31,24,16,8这些bits都为0,我们把这
9、儿个bits称为holes,注意在每个 byte的左边都有一个hole。检测0字节:如果longword中有一个字节的所有bit都为0,则进行加法后,从这个字节的右边的字节 传递来的进位都会落到这个字节的最低位所在的hole上,而从这个字节的最高位则永远不 会产生向左边字节的hole的进位。则这个字节左边的hole在进行加法后不会改变,由此可 以检测出0字节;相反,如果longword中所有字节都不为0,则每个字节中至少有1位为 1,进行加法后所有的hole都会被改变。为了便于理解,请看下面的例子:b3 b2 bl b031 0longword: XXXXXXXX XXXXXXXX 00000
10、000 XXXXXXXX+ magic_bits: 01111110 11111110 11111110 11111111上面longword P的b1为0, X可能为0也可能为1。因为b1的所有bit都为0,而从b0 传递过来的进位只可能是0或1,很显然b1永远也不会产生进位,所以加法后longword 的第16 bit这个hole不会变。(2) A Tongword这一步取出加法后longword中所有未改变的bit。(3) & magic_bits最后取出longword中未改变的hole,如果有任何hole未改变则说明longword中有为0的 字节。根据上面的描述,如果longwor
11、d中有为0的字节,则if中的表达式结果为非0,否则为0。NOTE:如果b3为10000000,则进行加法后第31 bit这个hole不会变,这说明我们无法检测出b3为10000000的所有WORD。值得庆幸的是用于strlen的字符串都是ASCII标准字符,其 值在0-127 Z间,这意味着每一个字节的第一个bit都为0。因此上面的算法是安全的。一旦检测出longword屮有为0的字节,后面的代码只需要找到第一个为0的字节并返回相 应的长度就0K:const char *cp = (const char*) (longword_ptr 一 1);if(cp0 = 0)return cp 一st
12、r;if(cpl=0)returncp -str +1;if(cp2=0)returncp -str +2;if(cp3=0)returncp -str +3;4.另一种实现1 size_t strlen_d(const char *str) 2const char *char_ptr;4 const ulong *longword_ptr;5 register ulong longword, himagic, lomagic;6for (char_ptr = str; (ulong)char_ptr8 & (sizeof (ulong) - 1) != 0;9 +char_ptr) 10 i
13、f (*char_ptr = *0 *)11 return char_ptr 一 str;12 13longword_ptr = (ulong*)char_ptr;himagic = 0x80808080L;lomagic = OxOlOlOlOlL;while (1) longword = *longword_ptr+;2425 const char *cp = (const char*) (longword_pti? - 1);2627if(cp0=0)28returncp 一 str;29if(cpl=0)30returncp 一 str + 1;31if(cp2=0)32returnc
14、p 一 str +2;33if(cp3:=0)34returncp 一 str + 3;35 36 37 上面的代码与strlen_c基本一样,不同的是: magic_bits 换成了 himagic 和 lomagichimagic = 0x80808080L;lomagic = OxOlOlOlOlL;以及if语句变得比较简单了if ( ( (longword 一 lomagic) & himagic) ! = 0)if语句中的计算可以分为如下2步:(1) Iongword - lomagichimagic和lomagic的二进制表示如I、:k3 b2 bl b031 0himagic:
15、10000000 10000000 10000000 10000000lomagic: 00000001 00000001 00000001 00000001在这种方法中假设所有字符都是ASCII标准字符,其值在0127之间,因此longword总是 如下形式:b3 b2 bl bO31 0longword: OXXXXXXX OXXXXXXX OXXXXXXX OXXXXXXX检测0字节:如果longword屮有一个字节的所有bit都为0,则进行减法后,这个字节的最高位一定会 从0变为相反,如果longword中所有字节都不为0,则每个字节中至少有1位为1, 进行减法后这个字节的最高位依然为
16、0a(2) & himagic这一步取出每个字节最高位的1,如果有任意字节最高位为1则说明longword中有为0的 字节。根据上面的描述,如果longword中有为0的字节,则if中的表达式结果为非0,否则为0。5.汇编实现VC CRT的汇编实现与前面strlen_c算法类似1 page , 1322 title strlen 一 return the length of a null-terminated st ring3 * * *4 ;strlen.asm 一 contains strlen () routine5 ;6 ; Copyright (c) Microsoft Corpor
17、ation. All rights reserved.7 ;8 ;Purpose:9 ; strlen returns the length of a null-terminated string,10 ; not including the null byte itself.11 ;12 ;*1313 xlist14 include cruntime inc15 .list1716 page9 . * * 20 ;strlen 一 return the length of a null-terminated string2528303335361 null byte424547Finds t
18、he length in bytes of the given string, not includkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk4950 CODESEG5151 public strlen5352 strlen proc 53 buf:ptr byte5654 OPTION PROLOGUE:NONE, EPILOGUE:NONE6061 stringequesp + 46263movecxf string;ecx - string64t estecx, 3;test if string is
19、align59 FPO(0, 1, 0, 0, 0, 0 )ed on 32 bits65jeshort main_loop6667str_misaligned:68;simple? byte loop until stringis aligned69moval,byte ptr ecx70addecx, 171testal, al72jeshort byte_373t estecx, 374jneshort str_misaligned7576addeax,dword ptr 0;5 byte nop to align 丄4bel below7778align16 /should be re
20、dundant7980main_loop:81moveaxz dword ptr ecx fread4 bytes82movedx,7efefeffh83addedx,eax84xoreax,-185xoreax,edx86addecx, 487testeaxf 81010100h88jeshort main_loop89;foundzero byte in the loop90moveax,ecx 一 491testalz al fis itbyte 092jeshort byte_093testah, ah fis itbyte 194jeshort byte_l95testeax,OOf
21、fOOOOh fis itbyte 296jeshort byte_297testeax,OffOOOOOOh98jeshort byte_399jmpshort main_loope clear and bit100101102byte_3:103leaeax,ecx - 1104movecx,string105subeax,ecx106ret107byte_2 :108leaeax,ecx 一 2109movecx,string110subeaxz ecx111:ret112byte_l:113leaeax,ecx 一 3114movecx,string115subeax,ecx116re
22、t117byte_0:118leaeax,ecx 一 4119movecx,string120subeax,ecx121ret122123strlen endp124125end;is it byte 3taken if bits 24-30 ar31 is set6.测试结果为了对上述各种实现的效率有一个大概的认识,我在VC8和GCC下分别进行了测试,测 试时均采用默认优化方式。下面是在GCC下运行儿百万次后的结果(在VC8下的运行结果 与此相似):strlen_a1: 515 ticks 0515 seconds2:375ticks0.375seconds3:375ti cks0.375s
23、econds4:375ticks0.375seconds5:375ti cks0.375secondstotal:2015ticks2.015secondsaverage:403ti cks0.403secondsstrlen_b1:360ticks0.36seconds2:390ti cks0.39seconds3:375ticks0.375seconds4:360ticks0.36seconds5 :375ticks0.375secondstotal:1860ti cks186secondsaverage:372ticks0.372secondsstrlen_c1:187ti cks0.1
24、87seconds2:172ticks0.172seconds3:187ticks0.187seconds4:187ticks0.187seconds5:188ti cks0.188secondstotal:921ticks0.921secondsaverage:184ti cks0.1842secondsstrlen_d1:172ticks0.172seconds2:187ticks0.187seconds3:172ticks0.172seconds4:187ti cks0.187seconds5 :188ticks0.188secondstOtdl:906ti cks0.906secondsaverage:181ticks0.1812secondsstrlen1:187ti cks0.187seconds2:172ticks0.172seconds3:188ti cks0.188seconds4:172ticks0.172seconds5 :187ticks0.187secondstotal:906ticks0.906secondsaverage:181ti cks0.1812seconds
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1