作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
终于开始glibc的代码学习了,这段时间纠结的事情太多,牵涉了不少精力。好,下面进入正题。
第一个函数strcpy:
- /* 代码风格应该是K&R风格 */
- char *
-
strcpy (dest, src)
-
char *dest;
-
const char *src;
-
{
-
reg_char c;
- /*
- 关于__unbounded和CHECK_BOUNDS_LOW,可以不作理会,可使其为空的宏定义。
- 引入这两个宏的原因是因为bounded pointer,其定义参见wiki:
- */
-
char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
/*
计算目的地址dest与源地址s的偏移-1的值。之所以要减去1,是因为后面的代码。
*/
-
const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
-
size_t n;
-
-
do
-
{
-
c = *s++;
- /* 前面计算偏移的时候多减了一个1,就因为上一个语句s进行了自加运算 */
-
s[off] = c;
-
}
-
while (c != '\0');
/* 这部分代码也可以无视 */
-
n = s - src;
-
(void) CHECK_BOUNDS_HIGH (src + n);
-
(void) CHECK_BOUNDS_HIGH (dest + n);
-
-
return dest;
- }
光看这部分代码,可能学不到什么东西,那么来想一下如果是自己去实现strcpy,会怎么写呢?
- char* my_strcpy1(char *dest, const char *src)
-
{
-
char *d = dest;
-
do {
-
*d++ = *src;
-
} while ('\0' != *src++);
-
-
return dest;
- }
下面对比一下C库和上面的代码,测试一下效率。为了公平,我复制了C库的代码,并去掉了bound pointer的相关代码,将其命名为my_strcpy2。然后复制2个字符串,重复一千万次。而且在编译的过程中,不使用任何优化参数。
- #include <stdio.h>
-
-
char* my_strcpy1(char *dest, const char *src)
-
{
-
char *d = dest;
-
do {
-
*d++ = *src;
-
} while ('\0' != *src++);
-
-
return dest;
-
}
-
-
/* Copy SRC to DEST. */
-
char *
-
my_strcpy2 (dest, src)
-
char *dest;
-
const char *src;
-
{
-
register char c;
-
char * s = (char *)src;
-
const int off = dest - s - 1;
-
-
do
-
{
-
c = *s++;
-
s[off] = c;
-
}
-
while (c != '\0');
-
-
return dest;
-
}
-
-
int main()
-
{
-
const char *str1 = "test1";
-
const char *str2 = "test2";
-
char buf[100];
-
-
int i;
-
for (i = 0; i < 10000000; ++i) {
-
my_strcpy1(buf, str1);
-
my_strcpy1(buf, str2);
-
}
-
-
return 0;
- }
当调用my_strcpy1时,其结果如下:
- [xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.373s
-
user 0m0.369s
-
sys 0m0.004s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.374s
-
user 0m0.368s
-
sys 0m0.004s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.373s
-
user 0m0.369s
- sys 0m0.004s
使用消耗在用户态的时间,三次的平均时间为0.369s
当调用my_strcpy2时,其结果如下:
- [xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.387s
-
user 0m0.383s
-
sys 0m0.004s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.385s
-
user 0m0.380s
-
sys 0m0.004s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.386s
-
user 0m0.380s
- sys 0m0.004s
同样取用户态的平均时间为0.381s。
看到这里,出乎了我的意料,因为C库的实现按理说应该更高效才对啊,但是事实不是这样。那么再次对比上面的代码,发现一个问题。my_strcpy1没有考虑到src和dest指向同一内存地址的情况,而glibc的代码my_strcpy2却没有问题——尽管在应用中,也不应该有这样的需求,strcpy的两个参数指向同一内存地址,但是作为一个库函数来说,必然却要做这样的考虑。Ok,那么修改my_strcpy1的代码,使其同样支持src和dest指向同一地址。
- char* my_strcpy1(char *dest, const char *src)
-
{
-
char *d = dest;
-
register char c;
-
-
do {
-
c = *src++;
-
*d++ = c;
-
} while ('\0' != c);
-
-
return dest;
-
}
-
-
/* Copy SRC to DEST. */
-
char *
-
my_strcpy2 (dest, src)
-
char *dest;
-
const char *src;
-
{
-
register char c;
-
char * s = (char *)src;
-
const int off = dest - s - 1;
-
-
do
-
{
-
c = *s++;
-
s[off] = c;
-
}
-
while (c != '\0');
-
-
return dest;
-
}
-
-
int main()
-
{
-
const char *str1 = "test1";
-
const char *str2 = "test2";
-
char buf[100];
-
-
int i;
-
for (i = 0; i < 10000000; ++i) {
-
my_strcpy1(buf, str1);
-
my_strcpy1(buf, str2);
-
}
-
-
return 0;
- }
结果如下:
- [xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.321s
-
user 0m0.318s
-
sys 0m0.003s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.296s
-
user 0m0.291s
-
sys 0m0.003s
-
[xxx@xxx-vm-fc13 test]$ time ./a.out
-
-
real 0m0.321s
-
user 0m0.320s
- sys 0m0.000s
这个版本的my_strcpy1增加了一个临时变量更高效了。这有些困惑啊。这让我搞不明白为什么C库要这样实现了。即使为了保证src不变,再引入一个新的局部变量
- char* my_strcpy1(char *dest, const char *src)
-
{
-
const char *s = src;
-
char *d = dest;
-
register char c;
-
-
do {
-
c = *s++;
-
*d++ = c;
-
} while ('\0' != c);
-
-
return dest;
- }
测试的结果仍然优于C库的实现。
那么C库究竟为什么要用偏移的方式来设置dest的内容呢?