8、特定区域的字符串比较和转换strcoll,strxfrm,wcscoll,wcsxfrm:strcoll使用当前的区域设置来比较字符串,strxfrm使用当前的区域设置来转换字符串。当前区域设置由LL_COLLATE宏指定。它们均调用带有区域设置参数的内部版本strcoll_l和strxfrm_l来完成实际的工作。
/* strcoll.c:strcoll函数的实现 */ #include <string.h> #ifndef STRING_TYPE # define STRING_TYPE char # define STRCOLL strcoll # define STRCOLL_L __strcoll_l # define USE_HIDDEN_DEF #endif #include "../locale/localeinfo.h" int STRCOLL (s1, s2) const STRING_TYPE *s1; const STRING_TYPE *s2; { return STRCOLL_L (s1, s2, _NL_CURRENT_LOCALE); } #ifdef USE_HIDDEN_DEF libc_hidden_def (STRCOLL) #endif
/* strxfrm.c:strxfrm函数的实现 */ #include <string.h> #include <locale/localeinfo.h> #ifndef STRING_TYPE # define STRING_TYPE char # define STRXFRM strxfrm # define STRXFRM_L __strxfrm_l #endif size_t STRXFRM (STRING_TYPE *dest, const STRING_TYPE *src, size_t n) { return STRXFRM_L (dest, src, n, _NL_CURRENT_LOCALE); }
9、错误消息报告strerror:获取错误码errnum的字符串描述,在下一次调用strerror之前这个字符串存储空间不能修改,对这个空间进行写操作会导致未定义的行为。若获取没有成功,则使用errno全局变量(或全局宏)中的错误码来获取错误消息。
/* strerror.c:strerror函数的实现 */ #include <libintl.h> /* 有很多内部接口 */ #include <stdio.h> #include <string.h> #include <errno.h> /* errno中保存了程序的错误码 */ /* 返回错误码errnum的字符串描述,在下一次调用strerror之前这个字符串存储 空间不能修改,对这个空间进行写操作会导致未定义的行为 */ libc_freeres_ptr (static char *buf); /* 存放字符串描述的全局空间 */ char * strerror (errnum) int errnum; { char *ret = __strerror_r (errnum, NULL, 0); /* 根据错误码获取错误消息 */ int saved_errno; if (__builtin_expect (ret != NULL, 1)) /* 若错误消息获取成功,则返回它 */ return ret; /* 否则获取errno中保存的程序错误码,用缓冲区buf来存储它的字符串描述 */ saved_errno = errno; if (buf == NULL) buf = malloc (1024); /* buf是一个全局缓冲区 */ __set_errno (saved_errno); /* 设置错误码 */ if (buf == NULL) return _("Unknown error"); return __strerror_r (errnum, buf, 1024); /* 获取错误码对应的错误消息并返回 */ }
10、内存块复制memcpy,memmove,wmemcpy,wmemmove:memcpy从SRC中复制N个字节的内容到DEST中,memmove从SRC中复制N个字节的内容到DEST中,保证对重叠字符串(即SRC与DEST共用存储空间)有正确的行为。这两个函数的实现使用了memcopy.h和pagecopy.h中定义的内部接口,有按字节方式复制BYTE_COPY_FWD,按字方式复制WORD_COPY_FWD(一个字为unsigned long型)、按页方式复制PAGE_COPY_FWD_MAYBE,这些接口都是以宏的形式提供的。
/* memcopy.h -- 在内存复制函数中使用的一些定义 */ /* 内存函数的复制策略是: 1、复制字节,直到目标指针被对齐。 2、在展开的循环中复制字。如果源指针和目标指针不是用 同一种方式对齐,则使用字内存操作,但在写之前要对两个读取的字进行移位和合并 3、复制剩下的几个字节 在至少有10个寄存器用来给GCC使用的处理器上,这是非常快速的,并且能在一条指令中使用 reg+const来访问内存 */ #include <sys/cdefs.h> #include <endian.h> /* 在本文件中定义的宏有: BYTE_COPY_FWD(dst_beg_ptr, src_beg_ptr, nbytes_to_copy) BYTE_COPY_BWD(dst_end_ptr, src_end_ptr, nbytes_to_copy) WORD_COPY_FWD(dst_beg_ptr, src_beg_ptr, nbytes_remaining, nbytes_to_copy) WORD_COPY_BWD(dst_end_ptr, src_end_ptr, nbytes_remaining, nbytes_to_copy) MERGE(old_word, sh_1, new_word, sh_2) */ /* 用于对齐的内存操作类型,正常时它应该是能一次装载和存储的最大类型 */ #define op_t unsigned long int #define OPSIZ (sizeof(op_t)) /* 用于未对齐的操作类型 */ typedef unsigned char byte; /* 用于在寄存器中存储字节的优化类型 */ #define reg_char char /* 对两个字的合并操作 */ #if __BYTE_ORDER == __LITTLE_ENDIAN /* 小端字节序:w0在低端,w1在高端 */ #define MERGE(w0, sh_1, w1, sh_2) (((w0) >> (sh_1)) | ((w1) << (sh_2))) #endif #if __BYTE_ORDER == __BIG_ENDIAN /* 大端字节序:w0在高端,w1在低端 */ #define MERGE(w0, sh_1, w1, sh_2) (((w0) << (sh_1)) | ((w1) >> (sh_2))) #endif /* 向前复制:从SRC_BP中精确地复制NBTYES个字节到DST_BP中,无需对指针的对齐作任何假设 */ #define BYTE_COPY_FWD(dst_bp, src_bp, nbytes) / do / { / size_t __nbytes = (nbytes); / while (__nbytes > 0) / { / byte __x = ((byte *) src_bp)[0]; / src_bp += 1; / __nbytes -= 1; / ((byte *) dst_bp)[0] = __x; / dst_bp += 1; / } / } while (0) /* 向后复制:从SRC_END_PTR中精确地复制NBTYTES_TO_COPY个字节到DST_END_PTR中, 复制从指针前面的字节右端开始,并且向着更小的地址方向前进。无需对指针的对齐作任何假设 */ #define BYTE_COPY_BWD(dst_ep, src_ep, nbytes) / do / { / size_t __nbytes = (nbytes); / while (__nbytes > 0) / { / byte __x; / src_ep -= 1; / __x = ((byte *) src_ep)[0]; / dst_ep -= 1; / __nbytes -= 1; / ((byte *) dst_ep)[0] = __x; / } / } while (0) /* 向前复制:从SRC_BP中复制最多NBYTES个字节到DST_BP中,假设DST_BP对齐到OPSIZ的倍数。 如果不是所有的字节都能顺利的复制,剩下的字节数保存到NBYTES_LEFT中,否则存入0 */ extern void _wordcopy_fwd_aligned (long int, long int, size_t) __THROW; extern void _wordcopy_fwd_dest_aligned (long int, long int, size_t) __THROW; #define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) / do / { / if (src_bp % OPSIZ == 0) / _wordcopy_fwd_aligned (dst_bp, src_bp, (nbytes) / OPSIZ); / else / _wordcopy_fwd_dest_aligned (dst_bp, src_bp, (nbytes) / OPSIZ); / src_bp += (nbytes) & -OPSIZ; / dst_bp += (nbytes) & -OPSIZ; / (nbytes_left) = (nbytes) % OPSIZ; / } while (0) /* 向后复制:从SRC_END_PTR中复制最多NBYTES_TO_COPY个字节到DST_END_PTR中,复制 从指针前面的字(为op_t类型)的右端开始,并且向着更小的地址方向前进。可以利用DST_END_PTR 已经对齐到OPSIZ的倍数。如果不是所有的字节都能顺利的复制,剩下的字节数保存到 NBYTES_REMAINING中,否则存入0 */ extern void _wordcopy_bwd_aligned (long int, long int, size_t) __THROW; extern void _wordcopy_bwd_dest_aligned (long int, long int, size_t) __THROW; #define WORD_COPY_BWD(dst_ep, src_ep, nbytes_left, nbytes) / do / { / if (src_ep % OPSIZ == 0) / _wordcopy_bwd_aligned (dst_ep, src_ep, (nbytes) / OPSIZ); / else / _wordcopy_bwd_dest_aligned (dst_ep, src_ep, (nbytes) / OPSIZ); / src_ep -= (nbytes) & -OPSIZ; / dst_ep -= (nbytes) & -OPSIZ; / (nbytes_left) = (nbytes) % OPSIZ; / } while (0) /* 进入展开循环的门槛值 */ #define OP_T_THRES 16
/* pagecopy.h -- 按页方式来复制的宏;用在memcpy和memmove中 */ /* 本文件中定义的宏: PAGE_COPY_FWD_MAYBE (dstp, srcp, nbytes_left, nbytes) 由WORD_COPY_FWD以及其他的函数来调用,指针至少要是字对齐的。这将会检查虚页复制是否能 执行、是否应该执行、以及如果能执行的话则执行它 依赖于系统的pagecopy.h文件应该定义以下宏,然后包含本文件: PAGE_COPY_THRESHOLD -- 值得使用按页复制策略的最小字节数 PAGE_SIZE -- 页的大小 PAGE_COPY_FWD (dstp, srcp, nbytes_left, nbytes) -- 执行虚页复制操作的宏。指针要对齐到PAGE_SIZE个字节的边界上 */ #if PAGE_COPY_THRESHOLD #include <assert.h> #define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) / do / { / if ((nbytes) >= PAGE_COPY_THRESHOLD && / PAGE_OFFSET ((dstp) - (srcp)) == 0) / { / /* 要复制的字节数超过内核用于复制虚拟页的VM操作的门槛值,且源地址 / 和目标地址有同样的对齐方式 */ / size_t nbytes_before = PAGE_OFFSET (-(dstp)); / if (nbytes_before != 0) / { / /* 首先复制第一页前面的各个字 */ / WORD_COPY_FWD (dstp, srcp, nbytes_left, nbytes_before); / assert (nbytes_left == 0); / nbytes -= nbytes_before; / } / PAGE_COPY_FWD (dstp, srcp, nbytes_left, nbytes); / } / } while (0) /* 页大小总是2的幂,这样我们就可以避免模除法运算 */ #define PAGE_OFFSET(n) ((n) & (PAGE_SIZE - 1)) #else #define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) #endif
/* memcpy.c:memcpy函数的实现 */ #include <string.h> #include <memcopy.h> /* 包含了字节复制函数BYTE_COPY_FWD和字复制函数WORD_COPY_FWD */ #include <pagecopy.h> /* 包含内存页复制函数PAGE_COPY_FWD_MAYBE */ #undef memcpy /* 从src中复制len个字节的内容到dst中 */ void * memcpy (dstpp, srcpp, len) void *dstpp; const void *srcpp; size_t len; { unsigned long int dstp = (long int) dstpp; unsigned long int srcp = (long int) srcpp; /* 从开始复制到末尾 */ /* 如果len足够长,使用字复制方式(一个字为long类型,一般占4个字节) */ if (len >= OP_T_THRES) { /* 复制开头的几个字节,以使dstp对齐到字的边界 */ len -= (-dstp) % OPSIZ; BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); /* 字节复制方式 */ /* 通过虚拟地址操作,从srcp中复制尽可能多的页到dstp中 */ PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); /* 页复制方式 */ /* 利用对齐的dstp,采用字复制方式从srcp复制到dstp。剩余的字节数放在第三个实参中, 例如放在len中。这个数字可能在不同的机器有不同的值 */ WORD_COPY_FWD (dstp, srcp, len, len); /* 复制剩下的尾部几个字节 */ } /* 还有剩下的几个字节,使用字节内存操作 */ BYTE_COPY_FWD (dstp, srcp, len); return dstpp; } libc_hidden_builtin_def (memcpy)
/* memmove.c:memmove函数的实现 */ #include <string.h> #include <memcopy.h> /* 包含了字节复制函数BYTE_COPY_FWD和字复制函数WORD_COPY_FWD */ #include <pagecopy.h> /* 包含内存页复制函数PAGE_COPY_FWD_MAYBE */ /* 所有这些都是为了在定义了一些东西后bcopy.c能包含本文件 */ #ifndef a1 #define a1 dest /* 第一个实参是dest */ #define a1const #define a2 src /* 第二个实参是src */ #define a2const const #undef memmove #endif #if !defined(RETURN) || !defined(rettype) #define RETURN(s) return (s) /* 返回dest */ #define rettype void * #endif /* 从SRC中复制LEN个字节的内容到DEST中,保证对重叠字符串(即SRC与DEST共用存储空间)有正确的行为 */ rettype memmove (a1, a2, len) a1const void *a1; a2const void *a2; size_t len; { unsigned long int dstp = (long int) dest; unsigned long int srcp = (long int) src; /* 这个测试使得向前复制代码一旦可能就能被使用,减少工作集 */ if (dstp - srcp >= len) /* *Unsigned* compare! */ { /* 从开始复制到末尾 */ /* 如果len足够长,使用字复制方式(一个字为long类型,一般占4个字节) */ if (len >= OP_T_THRES) { /* 复制开头的几个字节,以使dstp对齐到字的边界 */ len -= (-dstp) % OPSIZ; BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); /* 字节复制方式 */ /* 通过虚拟地址操作,从srcp中复制尽可能多的页到dstp中 */ PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); /* 页复制方式 */ /* 利用对齐的dstp,采用字复制方式从srcp复制到dstp。剩余的字节数放在第三个实参中, 例如放在len中。这个数字可能在不同的机器有不同的值 */ WORD_COPY_FWD (dstp, srcp, len, len); /* 复制剩下的尾部几个字节 */ } /* 还有剩下的几个字节,使用字节内存操作 */ BYTE_COPY_FWD (dstp, srcp, len); } else { /* 从开始复制到末尾 */ srcp += len; dstp += len; /* 如果len足够长,使用字复制方式(一个字为long类型,一般占4个字节) */ if (len >= OP_T_THRES) { /* 复制开头的几个字节,以使dstp对齐到字的边界 */ len -= dstp % OPSIZ; BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ); /* 利用对齐的dstp,采用字复制方式从srcp复制到dstp。剩余的字节数放在第三个实参中, 例如放在len中。这个数字可能在不同的机器有不同的值 */ WORD_COPY_BWD (dstp, srcp, len, len); /* 复制剩下的尾部几个字节 */ } /* 还有剩下的几个字节,使用字节内存操作 */ BYTE_COPY_BWD (dstp, srcp, len); } RETURN (dest); } #ifndef memmove libc_hidden_builtin_def (memmove) #endif
解释:
(1)memcopy.h中,宏op_t为字的类型,定义为unsigned long,OPSIZ为字的大小(32位平台中为4字节)。byte为字节的类型,定义为unsigned char。MERGE函数用于合并两个字,根据不同的机器字节序,一个字在高端,一个字在低端。字节复制和字复制都有两种方式,一种是向前复制,一种是向后复制。字复制时需要指针对齐到字的边界(即指针变量中的值为OPSIZ的倍数),复制操作使用了编译器内置的_wordcopy_fwd_aligned等函数。字节复制的接口中的代码是很直接的,用一个while循环一个字节一个字节地进行拷贝即可。宏OP_T_THRES定义了能进行字复制的最低门槛值。
(2)pagecopy.h中,要复制的字节数必须达到一定的门槛值PAGE_COPY_THRESHOLD(这个值在内核中定义),才会执行按页复制。PAGE_SIZE为页的大小,在内核中定义,PAGE_OFFSET(n)用于计算页的偏移。复制时先用WORD_COPY_FWD复制前面几个字节,这样就能让源地址和目标地址按页对齐,然后就可执行页复制。
(3)有了这些宏,memcpy和memmove函数的实现就比较简单了,直接用这些接口来进行复制操作,只不过要注意进行字复制或页复制时要复制开头的几个字节,以对齐到字或页的边界。最后尾部可能还剩下几个字节,用字节复制复制它们即可。
11、内存块中的字符搜索memchr,wmemchr:在内存块S的前N个字节中搜索C的第一次出现。算法实现与strlen及strchr类似。
/* memchr.c:memchr函数的实现 */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #undef __ptr_t /* 标准C++或标准C中通用指针为void*类型 */ #if defined (__cplusplus) || (defined (__STDC__) && __STDC__) # define __ptr_t void * #else /* 传统C中通用指针为char*类型 */ # define __ptr_t char * #endif #if defined _LIBC # include <string.h> # include <memcopy.h> #else # define reg_char char #endif #if HAVE_STDLIB_H || defined _LIBC # include <stdlib.h> #endif #if HAVE_LIMITS_H || defined _LIBC # include <limits.h> #endif #define LONG_MAX_32_BITS 2147483647 #ifndef LONG_MAX #define LONG_MAX LONG_MAX_32_BITS #endif #include <sys/types.h> #if HAVE_BP_SYM_H || defined _LIBC #include <bp-sym.h> #else # define BP_SYM(sym) sym #endif #undef memchr #undef __memchr /* 在S的前N个字节中搜索C的第一次出现 */ __ptr_t __memchr (s, c_in, n) const __ptr_t s; int c_in; size_t n; { const unsigned char *char_ptr; const unsigned long int *longword_ptr; unsigned long int longword, magic_bits, charmask; unsigned reg_char c; c = (unsigned char) c_in; /* 通过一次读取一个字符来处理开头的几个字符,直到char_ptr中的值对齐到一个long型字的边界, 即直到char_ptr中的值是long的字节数(通常为4)的倍数 */ for (char_ptr = (const unsigned char *) s; n > 0 && ((unsigned long int) char_ptr & (sizeof (longword) - 1)) != 0; --n, ++char_ptr) if (*char_ptr == c) /* 若到达字符c处,则直接返回其指针 */ return (__ptr_t) char_ptr; /* 所有这些说明性的注释使用4字节的long型字,但本算法同样也可以应用于8字节的long型字 */ longword_ptr = (unsigned long int *) char_ptr; /* magic_bits的第8,16,24,31位为0,称这些位为“洞”。注意每个字节的左边有一个洞, 在最后的位置上也有一个洞。 bits: 01111110 11111110 11111110 11111111 比特1确保进位能传播到后面的比特0上,比特0则提供洞,以便让进位陷进去 */ if (sizeof (longword) != 4 && sizeof (longword) != 8) abort (); #if LONG_MAX <= LONG_MAX_32_BITS magic_bits = 0x7efefeff; #else magic_bits = ((unsigned long int) 0x7efefefe << 32) | 0xfefefeff; #endif /* 设置一个长整型字,其每个字节都是字符c */ charmask = c | (c << 8); charmask |= charmask << 16; #if LONG_MAX > LONG_MAX_32_BITS charmask |= charmask << 32; #endif /* 这里我们不使用传统的对每个字符都进行测试的循环,而是一次测试一个long型字。技巧性的部分 是测试当前long型字的各个字节是否为0 */ while (n >= sizeof (longword)) { /* longword中有一个字节为C,恰好等价于longword ^ charmask中有一个字节为0 */ longword = *longword_ptr++ ^ charmask; /* 让longword加上魔数magic_bits */ if ((((longword + magic_bits) /* 设置那些通过加法而未改变的位 */ ^ ~longword) /* 只需看这些洞。如果任何的洞位都没有改变,最有可能的是有一个字节值为C或者到达终止符处(没找到C) */ & ~magic_bits) != 0) { /* 长整型字的哪个字节为C或0?如果都不是,则是一个非预期情况,继续搜索 */ const unsigned char *cp = (const unsigned char *) (longword_ptr - 1); if (cp[0] == c) return (__ptr_t) cp; if (cp[1] == c) return (__ptr_t) &cp[1]; if (cp[2] == c) return (__ptr_t) &cp[2]; if (cp[3] == c) return (__ptr_t) &cp[3]; #if LONG_MAX > 2147483647 /* 如果long类型是8个字节,则还有4个字节需要判断 */ if (cp[4] == c) return (__ptr_t) &cp[4]; if (cp[5] == c) return (__ptr_t) &cp[5]; if (cp[6] == c) return (__ptr_t) &cp[6]; if (cp[7] == c) return (__ptr_t) &cp[7]; #endif } n -= sizeof (longword); } /* 循环完如果还剩下几个字节,则继承搜索这剩下的几个字节 */ char_ptr = (const unsigned char *) longword_ptr; while (n-- > 0) { if (*char_ptr == c) return (__ptr_t) char_ptr; else ++char_ptr; } return 0; } #ifdef weak_alias weak_alias (__memchr, BP_SYM (memchr)) #endif libc_hidden_builtin_def (memchr)
12、内存块比较memcmp,wmemcmp:对两个内存块的前N个字节进行比较。比较也是使用字(unsigned long型)的比较方式,以加快搜索速度。采用的策略是先比较开头的几个字节,以使块指针对齐到字的边界,再用memcmp_common_alignment(两个内存块都对齐的情况)或memcmp_not_common_alignment(一个内存块对齐,而另一个没有对齐)按字进行快速的比较,最后对剩下的几个字节进行比较。代码就不再解剖了,涉及到大量的字操作,以及用MERGE进行字合并(这需要考虑到机器的字节序)。
13、内存块设置memset,wmemset:将内存块的前LEN个字节设置为字符C。也是采用字的方式来进行快速地写入。先设置了一个字cccc,其每个字节都是字符C。为了加快写入速度,每循环一次就写入8个cccc,最后对剩下的几个字节写入C。
/* memset.c:memset函数的实现 */ #include <string.h> #include <memcopy.h> #undef memset /* 将内存块DST的前LEN个字节设置为字符C */ void * memset (dstpp, c, len) void *dstpp; int c; size_t len; { long int dstp = (long int) dstpp; if (len >= 8) { size_t xlen; op_t cccc; /* 设置一个长整型字,其每个字节都是字符c */ cccc = (unsigned char) c; cccc |= cccc << 8; cccc |= cccc << 16; if (OPSIZ > 4) /* 移位操作分两步,以避免当long为32位时出现警告 */ cccc |= (cccc << 16) << 16; /* 把dstp对齐到字的边界,开头的几个字节要设置为C,在这个对齐循环中无需 测试LEN是否等于0 */ while (dstp % OPSIZ != 0) { ((byte *) dstp)[0] = c; dstp += 1; len -= 1; } /* 每次迭代中写8个op_t型的字,直到剩下不到8个字为止 */ xlen = len / (OPSIZ * 8); /* 计算迭代次数 */ while (xlen > 0) { ((op_t *) dstp)[0] = cccc; ((op_t *) dstp)[1] = cccc; ((op_t *) dstp)[2] = cccc; ((op_t *) dstp)[3] = cccc; ((op_t *) dstp)[4] = cccc; ((op_t *) dstp)[5] = cccc; ((op_t *) dstp)[6] = cccc; ((op_t *) dstp)[7] = cccc; dstp += 8 * OPSIZ; xlen -= 1; } len %= OPSIZ * 8; /* 计算剩下的字节数 */ /* 每次迭代写1个字,直到剩下不到OPSIZ个字节为止 */ xlen = len / OPSIZ; /* 计算迭代次数 */ while (xlen > 0) { ((op_t *) dstp)[0] = cccc; dstp += OPSIZ; xlen -= 1; } len %= OPSIZ; } /* 写入最后剩下的几个字节 */ while (len > 0) { ((byte *) dstp)[0] = c; dstp += 1; len -= 1; } return dstpp; } libc_hidden_builtin_def (memset)