astrotycoon

说到strlen,想必这是C标准库中提供的字符串操作函数最简单的一个了,相信大家都会很快的写出下面的代码:

int strlen( const char* str ){int length = 0;while ( *str++ )++length;return ( length );}

或者如下代码:

int strlen( const char* str ){const char* ptr = str;while ( *str++ );return ( str – ptr – 1 );}

但我们有没有想过这样做的效率如何呢?可不可以一次性判断几个字节来代替逐个字节判断这种传统的方式呢?就像memcpy那样,一次拷贝sizeof(unsigned long int)个字节一样。

果然glibc中采用了这种技巧。下面给出在32位计算机上的C语言实现(有改动, 假设unsigned long int 为4个字节):

size_t strlen(const char *str){const char *char_ptr = NULL;const unsigned long int *longword_ptr = NULL;register unsigned long int longword, magic_bits;for (char_ptr = str; ((unsigned long int)(char_ptr)& (sizeof(unsigned long int) – 1)) != 0; ++char_ptr){if (*char_ptr == ‘\0’)return char_ptr – str;}longword_ptr = (const unsigned long int *)char_ptr;magic_bits = 0x7efefeffL;for ( ;; ){longword = *longword_ptr++;if ((((longword + magic_bits) ^ ~longword) & ~magic_bits) != 0){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;}}}

首先,我们理清程序的思路,可以归纳为如下三点:

1.C标准库要求有很好的移植性,在绝大部分系统体系结构下都应该能正确运行。那么每次拿出4个字节比较(unsigned long int),就需要考虑内存对齐问题,传入的字符串的首字符地址可不一定在4对齐的地址上。如果在内存对齐之前就遇到了’\0′ 则直接return推出。否则到下一步;

2.一次读入并判断一个4字节(sizeof(unsigned long int))大小的内存块,如果4字节中没有为0的字节,则继续下一个4字节,否则到下一步;

3.到这里4字节中至少有一个字节为0,最后要做的就是找出这个字节的位置并return推出。

什么是数据对齐呢?

数据对齐(data alignment),是指所在的内存地址必须是该数据长度的整数倍,这样CPU的存取速度就会最快。比如在32位计算机中,一个int型的数据为4个字节,则int型数据的起始地址能被4整除的时候CPU的存取效率比较高。CPU的优化规则如下:对于n字节(n = 2, 4, 8…)的元素,它的首地址能被n整除才能获得最好的性能。

for (char_ptr = str; ((unsigned long int)(char_ptr)& (sizeof(unsigned long int) – 1)) != 0; ++char_ptr){if (*char_ptr == ‘\0’)return char_ptr – str;} 上面这段代码正是实现了数据对齐,完成了上述的第1点。

因为char_ptr已经4字节对齐,所以

longword_ptr = (const unsigned long int *)char_ptr;中的强制转换是安全的!

在接着分析之前,我们先来认识一下程序中magic_bits这个魔数,我们将它按bit展

0x7efefeff:01111110-11111110-11111110-11111111

我们注意到有4个红色的0,它们的位置被称为holes位,它们都有一个特征,就是在每个字节的左边,这有什么用呢?再联想一下,在左边,什么时候会被修改? 很明显,当右边有进位时,会修改到这个0,或者这几个0的位置与另外一个数相运算时会被改变。

下面着重分析:

(((longword + magic_bits) ^ ~longword) & ~magic_bits) != 0我们将((longword + magic_bits) ^ ~longword) & ~magic_bits语句以运算符&为基准分成两部分来看:左表达式 ((longword + magic_bits) ^ ~longword) 和

右表达式 ~magic_bits。有点编程经验的都知道在if判断语句中&的用处是用于判断 左表达式 中相应位是1还是0,那么相应位是指哪些位呢?相应位指的是与 右表达式中为1的位 相同的位。举个例子吧:假设我有个int型的数据ival, 现在我们要判断ival的第15位是1还是0,那么我们可以这样写:

if (ival & (1 << 15)){// do something}如果第15位是1,那么ival & (1 << 15)为真,否则为假。~magic_bits的二进制形式为: 10000001-00000001-00000001-00000000,可以看出为1的位置正是相应holes的位置,那么可以得出(((longword + magic_bits) ^ ~longword) & ~magic_bits) != 0的作用是在判断((longword + magic_bits) ^ ~longword)中的相应holes为是0还是1,由最后的!=0得知当((longword + magic_bits) ^ ~longword)中相应holes位置一个或多个为1时,进入if判断语句块。那么现在的问题是((longword + magic_bits) ^ ~longword)是什么意思呢?我们假设result = longword + magic_bits, 那么result中相应holes位置的值就会根据longword的实际值而设置:如果longword中某个字节是0,,那么与magic_bits对应字节相加就不会产生进位,那么result中相应holes位置就不会设置为0; 当longword中某个字节是非零时,结果恰恰相反– 与magic_bits对应字节相加会产生进位,则result中相应holes位置就会设置为1。最后一个问题是:result ^ ~longword是什么意思呢?我们假设a = result ^ ~longword。

我们举个具体的例子吧,如下(注意:longword中的各个字节的最高位不可能为1,因为ascii码范围是[0-127])

当你开展的事业从事的行动穷途末路大势已去的时候,

astrotycoon

相关文章:

你感兴趣的文章:

标签云: