strlen 函数的参数
strlen 以一个字符型指针为参数,使用 const 修饰符保护字符串的值不被改变。
strlen 函数的常见实现
strlen 函数的一个常见实现是依次遍历每个字符,如果不为 ‘\0’,则给计数变量加 1,遇到了 ‘\0’ 则返回计数变量的值。这种实现方式当字符串长度很短时有很好的性能,当字符串较长时就有点捉襟见肘了。
常见实现引发的思考
如果将上述常见实现作为一个天花板,它的短板在于处理长字符串时的不足,这个不足的关键又在于扫描长度为一个字符的限制。如果能够一次扫描多个元素,那么在计数长字符串时就能够得到比常见实现更好的性能。
从一次扫描一个元素到一次扫描多个元素,扫描次数的减少将带来更好的性能。这样的思考让我联想到了中断触发频率问题、read or write 读写单元大小问题、tcp/ip 中的 low-water 问题。这几个问题有着相同的特点。
strlen 中每次扫描字符的个数相当于中断触发的频率、读写单元大小、low-water 大小。strlen 相较于其它问题而言没有太复杂的前期过程,仅仅需要传递一个字符型指针为参数。在这种环境下,提高 strlen 函数每次扫描字符的数量所带来的性能提高并不显著。
glibc 中 strlen 函数的实现
glibc 内建的 strlen 首先对字符串的前几个未按照 longword 对齐的地址依次扫描,如果扫描到字符串结尾则返回字符串长度退出。这里使用一个临时的字符指针来保存当前正在扫描的字符地址,该地址与字符串起始地址做差就能得到字符串的长度。
当字符的地址按照 longword 对齐,则以 longword 大小为扫描单位。首先生成 himagic 与 lomagic 掩码,仅支持 longword 长度为 4 字节或 8 字节,其它字长则调用 abort 函数终止整个进程。
制作好掩码后按照 longword 大小扫描待处理的字符串。通过执行下面的代码来测试 longword 中是否有结束字符 ‘\0’。
if (((longword - lomagic) & ~longword & himagic) != 0)
当发现有 longword 中存在结束字符时, 判断结束字符出现的位置,不存在结束字符则继续扫描下一个 longword 大小的单元。使用下面的代码来获取新的 longword 的值,遍历字符串。
longword = *longword_ptr++;
longword 中保存的是旧值。当发现 longword 中存在结束字符时,首先将 longword_ptr 减一,使其指向当前 longword 的地址。代码如下:
const char *cp = (const char *) (longword_ptr - 1);
longword_ptr 先减一然后再转化为 const char * 类型,注意这里的 longword_ptr 为无符号长整型指针类型,对它减一意味着对地址减去一个无符号长整型数据的大小。
这之后依次查询结束字符的位置,找到结束字符在 longword 中的地址,该地址与字符串首地址的差值就是字符串的长度。
对于掩码部分的分析需要进一步思考,现在暂时跳过!