学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效。恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试。
strlen的函数原形如下:
size_t strlen(const char *str);
strlen返回str中字符的个数,其中str为一个以'\0'结尾的字符串(a null-terminated string)。
1. 简单实现
如果不管效率,最简单的实现只需要4行代码:
| 2 3 4 5 6 7 | size_t strlen_a(const char *str) { size_t length = 0 ; while (*str++ ) ++ length; return length; } |
也许可以稍加改进如下:
| 2 3 4 5 6 | size_t strlen_b(const char *str) { const char *cp = str; while (*cp++ ); return (cp - str - 1 ); } |
2. 高效实现
很显然,标准库的实现肯定不会如此简单,上面的strlen_a以及strlen_b都是一次判断一个字符直到发现'\0'为止,这是非常低效的。比较高效的实现如下(在这里WORD表示计算机中的一个字,不是WORD类型):
(1) 一次判断一个字符直到内存对齐,如果在内存对齐之前就遇到'\0'则直接return,否则到(2);
(2) 一次读入并判断一个WORD,如果此WORD中没有为0的字节,则继续下一个WORD,否则到(3);
(3) 到这里则说明WORD中至少有一个字节为0,剩下的就是找出第一个为0的字节的位置然后return。
NOTE:
数据对齐(data alignment),是指数据所在的内存地址必须是该数据长度的整数倍,这样CPU的存取速度最快。比如在32位的计算机中,一个WORD为4 byte,则WORD数据的起始地址能被4整除的时候CPU的存取效率比较高。CPU的优化规则大概如下:对于n字节(n = 2,4,8...)的元素,它的首地址能被n整除才能获得最好的性能。
为了便于下面的讨论,这里假设所用的计算机为32位,即一个WORD为4个字节。下面给出在32位计算机上的C语言实现(假设unsigned long为4个字节):源码来着于glibc
| 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
typedef unsigned long ulong; size_t strlen_c(const char *str) { const char *char_ptr; const ulong *longword_ptr; register ulong longword, magic_bits; for (char_ptr = str; ((ulong)char_ptr & (sizeof(ulong) - 1)) != 0 ; ++ char_ptr) { if (*char_ptr == '\0' ) return char_ptr - str; } longword_ptr = (ulong * )char_ptr; magic_bits = 0x7efefeffL ; while (1 ) { 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 ; } } } |
3. 源码剖析
上面给出的C语言实现虽然不算特别复杂,但也值得花点时间来弄清楚,先看9-14行:
for (char_ptr = str; ((ulong)char_ptr & (sizeof(ulong) - 1)) != 0; ++char_ptr) {
if (*char_ptr == '\0')
return char_ptr - str;
}
上面的代码实现了数据对齐,如果在对齐之前就遇到'\0'则可以直接return char_ptr - str;
测试代码如下:
| 2 3 4 | char szName[]=”Jack”; char *p=szName; p++;//p移动一个字节,本身的地址p是按照4个字节对齐的,移动后不再对齐 printf(“%d\n”,strlen(p)); |
第16行将longword_ptr指向数据对齐后的首地址longword_ptr = (ulong*)char_ptr;
第18行给magic_bits赋值(在后面会解释这个值的意义)