高效的strlen函数

来源:互联网 发布:2016詹姆斯数据统计 编辑:程序博客网 时间:2024/05/23 10:33


一般来说,一个入门的程序员大概可以不费劲的写出这样的 strlen 代码。

size_t my_strlen(const char * str) {<span style="white-space:pre"></span>size_t length = 0 ;<span style="white-space:pre"></span>while (*str++ )<span style="white-space:pre"></span>++ length;<span style="white-space:pre"></span>return  length;}


那我们再来看看,glibc函数库里面的strlen是如何实现的。

#include <string.h>#include <stdlib.h>
<pre name="code" class="cpp">size_t strlen(const char *str) {    const char *char_ptr;    const unsigned long int *longword_ptr;    unsigned long int longword, himagic, lomagic;    /* Handle the first few characters by reading one character at a time.       Do this until CHAR_PTR is aligned on a longword boundary.  */    for (char_ptr = str; ((unsigned long int) char_ptr            & (sizeof (longword) - 1)) != 0;            ++char_ptr)        if (*char_ptr == '\0')            return char_ptr - str;    /* All these elucidatory comments refer to 4-byte longwords,       but the theory applies equally well to 8-byte longwords.  */    longword_ptr = (unsigned long int *) char_ptr;    /* Bits 31, 24, 16, and 8 of this number are zero.  Call these bits       the "holes."  Note that there is a hole just to the left of       each byte, with an extra at the end:       bits:  01111110 11111110 11111110 11111111       bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD       The 1-bits make sure that carries propagate to the next 0-bit.       The 0-bits provide holes for carries to fall into.  */    himagic = 0x80808080L;    lomagic = 0x01010101L;    if (sizeof (longword) > 4) {        /* 64-bit version of the magic.  */        /* Do the shift in two steps to avoid a warning if long has 32 bits.  */        himagic = ((himagic << 16) << 16) | himagic;        lomagic = ((lomagic << 16) << 16) | lomagic;    }    if (sizeof (longword) > 8)        abort();    /* Instead of the traditional loop which tests each character,       we will test a longword at a time.  The tricky part is testing       if *any of the four* bytes in the longword in question are zero.  */    for (;;) {        longword = *longword_ptr++;        if (((longword - lomagic) & ~longword & himagic) != 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;            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;            }        }    }}

很明显这段代码比我们自己写的版本要复杂多了。其中注释写的比较清楚,我也不翻译了。

下面主要对其中几个点做分析吧。如果要看更详细的,请点击《strlen源码剖析》


简单版本和glibc版本主要的区别在于:通过内存对齐,来加快CPU的读取速度

(在计算机在读取内存中的数据的时候,在内存对齐的状态下一次读取一个word的数据,是最节省时间的。例如在32位的计算机中,一个WORD为4 byte,则WORD数据的起始地址能被4整除的时候CPU的存取效率比较高。


其中用到了两个技巧:

(1)由于传进来的字符串的地址有可能不是4字节(long int)对其的,因此首先找到4字节对其的那个地址
(2)技巧就是如何高效的判断在读取的4个字节中是否有字节为0


因此这个算法的整体思路如下:

(1)从字符串的开头开始,一次判断一个字符直到内存对齐(也就是地址能被4整除)。

(2)如果在内存对齐之前,字符串就已经结束了(也就是读取到'\0'),那么就return,否则到继续下一步;

(3)一次读入并判断一个DWORD,如果此DWORD中没有为0的字节,则继续下一个DWORD;如果判断出有'\0',就跳到下一步。

(4)找出该DWORD中第一个为0的字节的位置,然后return。



    IT从业人员需要及时关注技术动态,但是互联网的内容却过于离散,好东西往往隐藏得很深。现在可以在微信公众号里面
    搜索关注“小鱼儿的IT技术分享”,订阅我为大家每天汇总的IT技术信息。欢迎大家关注!
      有兴趣的请点击《与IT技术相关的微信公众号》

0 0