3、字符串复制strcpy,strncpy,wcscpy,wcsncpy:将字符串src(或其前n个字符)复制到dest中,覆盖dest的内容。实现中先检查指针是否越界,计算指针dest到src的偏移,然后开始做复制操作,复制到dest的开始位置处,以覆盖dest的内容。对strncpy,也采用了每4个字符作为一组来进行复制的方法,以加快复制速度。
-
- #include <stddef.h> /* 用到了ptrdiff_t */
- #include <string.h>
- #include <memcopy.h>
- #include <bp-checks.h> /* 定义了CHECK_BOUNDS_LOW和CHECK_BOUNDS_HIGH */
- #undef strcpy
-
- char *
- strcpy (dest, src)
- char *dest;
- const char *src;
- {
- reg_char c;
-
- char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
-
- const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
- size_t n;
- do
- {
- c = *s++;
- s[off] = c;
- }
- while (c != '/0');
- n = s - src;
- (void) CHECK_BOUNDS_HIGH (src + n);
- (void) CHECK_BOUNDS_HIGH (dest + n);
- return dest;
- }
- libc_hidden_builtin_def (strcpy)
-
- #include <string.h>
- #include <memcopy.h>
- #undef strncpy
-
-
- char *
- strncpy (s1, s2, n)
- char *s1;
- const char *s2;
- size_t n;
- {
- reg_char c;
- char *s = s1;
- --s1;
- if (n >= 4)
- {
- size_t n4 = n >> 2;
- for (;;)
- {
- c = *s2++;
- *++s1 = c;
- if (c == '/0')
- break;
- c = *s2++;
- *++s1 = c;
- if (c == '/0')
- break;
- c = *s2++;
- *++s1 = c;
- if (c == '/0')
- break;
- c = *s2++;
- *++s1 = c;
- if (c == '/0')
- break;
- if (--n4 == 0)
- goto last_chars;
- }
- n = n - (s1 - s) - 1;
- if (n == 0)
- return s;
- goto zero_fill;
- }
- last_chars:
- n &= 3;
- if (n == 0)
- return s;
- do
- {
- c = *s2++;
- *++s1 = c;
- if (--n == 0)
- return s;
- }
- while (c != '/0');
- zero_fill:
- do
- *++s1 = '/0';
- while (--n > 0);
- return s;
- }
- libc_hidden_builtin_def (strncpy)
4、字符串求长strlen,wcslen:返回str中终止符'/0'之前的字符个数。这里通过把指针移到终止符处,然后计算该指针与开始处指针的差值来获取字符串的长度。为了加快移动速度,实现中把const char*型指针char_ptr转换成了unsigned long*型指针longword_ptr,这样一次就可以移动4个字节。算法关键是要辨别出longword_ptr指向的值(有4个字节)中有一个字节为0(它表示字符'/0'),这说明指针到达了终止符'/0'处,要停止移动,并转换回const char*型指针,计算指针之间的差值。
-
- #include <string.h>
- #include <stdlib.h> /* 用到abort()函数 */
- #undef strlen
-
- size_t
- strlen (str)
- const char *str;
- {
- const char *char_ptr;
- const unsigned long int *longword_ptr;
- unsigned long int longword, magic_bits, himagic, lomagic;
-
-
- for (char_ptr = str; ((unsigned long int) char_ptr
- & (sizeof (longword) - 1)) != 0;
- ++char_ptr)
- if (*char_ptr == '/0')
- return char_ptr - str;
-
-
- longword_ptr = (unsigned long int *) char_ptr;
-
-
-
-
- magic_bits = 0x7efefeffL;
- himagic = 0x80808080L;
- lomagic = 0x01010101L;
- if (sizeof (longword) > 4)
- {
-
-
-
- magic_bits = ((0x7efefefeL << 16) << 16) | 0xfefefeffL;
- himagic = ((himagic << 16) << 16) | himagic;
- lomagic = ((lomagic << 16) << 16) | lomagic;
- }
- if (sizeof (longword) > 8)
- abort ();
-
-
- for (;;)
- {
- longword = *longword_ptr++;
- if (
- #if 0
-
- (((longword + magic_bits)
-
- ^ ~longword)
-
-
- & ~magic_bits)
- #else
- ((longword - lomagic) & himagic)
- #endif
- != 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;
- 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;
- }
- }
- }
- }
- libc_hidden_builtin_def (strlen)
解释:
(1)先移动char_ptr,使其值对齐到长整型字的边界,即移动到使char_ptr中的值是4的倍数为止。在对齐过程中若到达了终止符处,则直接返回与开始处的指针str的差值。
(2)对longword_ptr指针进行移动时,指针指向的值为longword。为了判断指针是否到达终止符,算法实现使用了两个魔数lomagic和himagic,lomagic各个字节的最低位为1,其余位均为0;himagic各个字节的最高位为1,其余位均为0。看表达式(longword - lomagic) & himagic,若longword中有一个字节为00000000,则减00000001时要向高字节借一位,得到11111111或11111110(当借了一位给更低的字节时),与10000000做“与”运算后变成10000000,这时表达式的结果必定不为0。可见,只有在表达式结果不等于0时,longword中才有可能有终止符。因此,在移动过程中,一旦表达式结果不等于0,只要逐一检查一下每个字节,看哪个为0,这时就到达终止符,计算指针差值并返回。若都不为0,则继续移动。
(3)也可以只用一个魔数magic_bits来实现,即代码中用#if 0注释掉的那部分,它可以达到同样的效果。看表达式((longword + magic_bits) ^ ~longword) & ~magic_bits,最后的“与”运算会使结果的其他位清零,只留下那4个洞位,因此我们只要看longword的洞位的变化即可。若longword中有一个字节为0,则做加法后它不可能向高字节(即左侧字节)进位,左侧字节的洞位没有改变(因为magic_bits的对应洞位为0,加上0而又没有低字节的进位,因此不会改变)。做异或运算后,必定使这个洞位变成1,因此表达式的结果必定不为0。可见,这跟(2)用两个魔数实现的效果是一样的。
5、字符搜索strchr,strrchr,wcschr,wcsrchr:在字符串s中查找字符c的第一次(或最后一次)出现,若没找到则返回NULL指针。算法实现与strlen类似,只不过在strlen是搜索到终止符'/0'为止,这里是搜索到字符c为止。
-
- #include <string.h>
- #undef strrchr
-
- char *
- strrchr (const char *s, int c)
- {
- register const char *found, *p;
- c = (unsigned char) c;
-
- if (c == '/0')
- return strchr (s, '/0');
- found = NULL;
- while ((p = strchr (s, c)) != NULL)
- {
- found = p;
- s = p + 1;
- }
- return (char *) found;
- }
- #ifdef weak_alias
- #undef rindex
- weak_alias (strrchr, rindex)
- #endif
- libc_hidden_builtin_def (strrchr)
解释:
(1)算法中,有可能搜索到字符c,也有可能搜索到终止符(当字符串中没有c时)。对于搜索到终止符,与strlen中一样,对于搜索到字符c,要判断longword中是否有一个字节为c,看表达式(((longword ^ charmask) + magic_bits) ^ ~(longword ^ charmask)) & ~magic_bits,长整型字charmask的每个字节都是字符c。strlen的对应表达式中的longword换成了这里的longword ^ charmask,而这里的longword中有一个字节为c,恰好等价于longword ^ charmask中有一个字节为0,因此具体的分析过程是一样的。
(2)strrchr的实现直接使用strchr。用strchr不停地向前搜索,直到搜索到最后一个c为止。
6、子串的无顺序匹配strspn,strcspn,strpbrk,wcsspn,wcscspn,wcspbrk:strspn和strcspn在s的开头查找一个最长子串,使其所有字符都在accept中(或都不在reject中),返回这个子串的长度。strspn的实现中直接对s中开头的每个字符搜索accept,看其是否在accept中。strcspn的实现则使用了strchr来查找字符。strpbrk在s中搜索第一个出现在accept中的字符,返回其指针。
-
- #include <string.h>
- #undef strspn
-
- size_t
- strspn (s, accept)
- const char *s;
- const char *accept;
- {
- const char *p;
- const char *a;
- size_t count = 0;
- for (p = s; *p != '/0'; ++p)
- {
- for (a = accept; *a != '/0'; ++a)
- if (*p == *a)
- break;
- if (*a == '/0')
- return count;
- else
- ++count;
- }
- return count;
- }
- libc_hidden_builtin_def (strspn)
-
- #if HAVE_CONFIG_H
- # include <config.h>
- #endif
- #if defined _LIBC || HAVE_STRING_H
- # include <string.h>
- #else
- # include <strings.h>
- # ifndef strchr
- # define strchr index
- # endif
- #endif
- #undef strcspn
-
- size_t
- strcspn (s, reject)
- const char *s;
- const char *reject;
- {
- size_t count = 0;
- while (*s != '/0')
- if (strchr (reject, *s++) == NULL)
- ++count;
- else
- return count;
- return count;
- }
- libc_hidden_builtin_def (strcspn)
-
- #ifdef HAVE_CONFIG_H
- # include <config.h>
- #endif
- #if defined _LIBC || defined HAVE_CONFIG_H
- # include <string.h>
- #endif
- #undef strpbrk
-
- char *
- strpbrk (s, accept)
- const char *s;
- const char *accept;
- {
- while (*s != '/0')
- {
- const char *a = accept;
- while (*a != '/0')
- if (*a++ == *s)
- return (char *) s;
- ++s;
- }
- return NULL;
- }
- libc_hidden_builtin_def (strpbrk)
7、模式匹配及字符串解析strstr,strtok,wcsstr,wcstok:strstr(src,sub)在src中搜索子串sub,返回其第一次出现的位置。strtok(str,set)用set中的字符作为分隔符把str分解为多个标号。
strstr的实现用了最新的二路模式匹配算法,可以达到最好的效率。由于算法比较复杂,涉及到很多内部函数,这里就不解剖了,我们平时一般使用KMP算法来进行模式匹配,这个效率也已经非常不错了。strtok实现如下:
-
- #include <string.h>
- static char *olds;
- #undef strtok
-
-
-
-
-
-
-
-
- char *
- strtok (s, delim)
- char *s;
- const char *delim;
- {
- char *token;
- if (s == NULL)
- s = olds;
-
- s += strspn (s, delim);
- if (*s == '/0')
- {
- olds = s;
- return NULL;
- }
-
- token = s;
- s = strpbrk (token, delim);
- if (s == NULL)
-
- olds = __rawmemchr (token, '/0');
- else
- {
-
- *s = '/0';
- olds = s + 1;
- }
- return token;
- }
算法先用strspn函数使s路过分隔符,移到当前记号的开始处;接着让token指向当前记号开始处,s移到下一个分隔符处(通过strpbrk函数);然后把这个分隔符替换成终止符'/0',以解析出当前记号,olds指向下一记号的开始处。下一次解析时,若指定s为NULL,则从olds处开始新的解析。