Chinaunix首页 | 论坛 | 博客
  • 博客访问: 33354
  • 博文数量: 34
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 30
  • 用 户 组: 普通用户
  • 注册时间: 2014-05-15 22:44
文章分类
文章存档

2014年(34)

我的朋友

分类: C/C++

2014-05-15 23:40:36

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
  

终于开始glibc的代码学习了,这段时间纠结的事情太多,牵涉了不少精力。好,下面进入正题。
第一个函数strcpy:
  1. /* 代码风格应该是K&R风格 */
  2. char *
  3. strcpy (dest, src)
  4.      char *dest;
  5.      const char *src;
  6. {
  7.   reg_char c;
  8.   /*
  9.   关于__unbounded和CHECK_BOUNDS_LOW,可以不作理会,可使其为空的宏定义。
  10.   引入这两个宏的原因是因为bounded pointer,其定义参见wiki:
  11.   */
  12.   char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
   /*
   计算目的地址dest与源地址s的偏移-1的值。之所以要减去1,是因为后面的代码。
   */
  1.   const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
  2.   size_t n;

  3.   do
  4.     {
  5.       c = *s++;
  6.       /* 前面计算偏移的时候多减了一个1,就因为上一个语句s进行了自加运算 */
  7.       s[off] = c;
  8.     }
  9.   while (c != '\0');
  
   /* 这部分代码也可以无视 */
  1.   n = s - src;
  2.   (void) CHECK_BOUNDS_HIGH (src + n);
  3.   (void) CHECK_BOUNDS_HIGH (dest + n);

  4.   return dest;
  5. }
光看这部分代码,可能学不到什么东西,那么来想一下如果是自己去实现strcpy,会怎么写呢?
  1. char* my_strcpy1(char *dest, const char *src)
  2. {
  3.     char *d = dest;
  4.     do {
  5.         *d++ = *src;
  6.     } while ('\0' != *src++);

  7.     return dest;
  8. }
下面对比一下C库和上面的代码,测试一下效率。为了公平,我复制了C库的代码,并去掉了bound pointer的相关代码,将其命名为my_strcpy2。然后复制2个字符串,重复一千万次。而且在编译的过程中,不使用任何优化参数。

  1. #include <stdio.h>

  2. char* my_strcpy1(char *dest, const char *src)
  3. {
  4.     char *d = dest;
  5.     do {
  6.         *d++ = *src;
  7.     } while ('\0' != *src++);

  8.     return dest;
  9. }

  10. /* Copy SRC to DEST. */
  11. char *
  12. my_strcpy2 (dest, src)
  13.      char *dest;
  14.      const char *src;
  15. {
  16.   register char c;
  17.   char * s = (char *)src;
  18.   const int off = dest - s - 1;

  19.   do
  20.     {
  21.       c = *s++;
  22.       s[off] = c;
  23.     }
  24.   while (c != '\0');

  25.   return dest;
  26. }

  27. int main()
  28. {
  29.     const char *str1 = "test1";
  30.     const char *str2 = "test2";
  31.     char buf[100];

  32.     int i;
  33.     for (i = 0; i < 10000000; ++i) {
  34.         my_strcpy1(buf, str1);
  35.         my_strcpy1(buf, str2);
  36.     }
  37.     
  38.     return 0;
  39. }
当调用my_strcpy1时,其结果如下:
  1. [xxx@xxx-vm-fc13 test]$ time ./a.out

  2. real 0m0.373s
  3. user 0m0.369s
  4. sys 0m0.004s
  5. [xxx@xxx-vm-fc13 test]$ time ./a.out

  6. real 0m0.374s
  7. user 0m0.368s
  8. sys 0m0.004s
  9. [xxx@xxx-vm-fc13 test]$ time ./a.out

  10. real 0m0.373s
  11. user 0m0.369s
  12. sys 0m0.004s
使用消耗在用户态的时间,三次的平均时间为0.369s

当调用my_strcpy2时,其结果如下:
  1. [xxx@xxx-vm-fc13 test]$ time ./a.out

  2. real 0m0.387s
  3. user 0m0.383s
  4. sys 0m0.004s
  5. [xxx@xxx-vm-fc13 test]$ time ./a.out

  6. real 0m0.385s
  7. user 0m0.380s
  8. sys 0m0.004s
  9. [xxx@xxx-vm-fc13 test]$ time ./a.out

  10. real 0m0.386s
  11. user 0m0.380s
  12. sys 0m0.004s
同样取用户态的平均时间为0.381s。


看到这里,出乎了我的意料,因为C库的实现按理说应该更高效才对啊,但是事实不是这样。那么再次对比上面的代码,发现一个问题。my_strcpy1没有考虑到src和dest指向同一内存地址的情况,而glibc的代码my_strcpy2却没有问题——尽管在应用中,也不应该有这样的需求,strcpy的两个参数指向同一内存地址,但是作为一个库函数来说,必然却要做这样的考虑。Ok,那么修改my_strcpy1的代码,使其同样支持src和dest指向同一地址。
  1. char* my_strcpy1(char *dest, const char *src)
  2. {
  3.     char *d = dest;
  4.     register char c;
  5.     
  6.     do {
  7.         c = *src++;
  8.         *d++ = c;
  9.     } while ('\0' != c);

  10.     return dest;
  11. }

  12. /* Copy SRC to DEST. */
  13. char *
  14. my_strcpy2 (dest, src)
  15.      char *dest;
  16.      const char *src;
  17. {
  18.   register char c;
  19.   char * s = (char *)src;
  20.   const int off = dest - s - 1;

  21.   do
  22.     {
  23.       c = *s++;
  24.       s[off] = c;
  25.     }
  26.   while (c != '\0');

  27.   return dest;
  28. }

  29. int main()
  30. {
  31.     const char *str1 = "test1";
  32.     const char *str2 = "test2";
  33.     char buf[100];

  34.     int i;
  35.     for (i = 0; i < 10000000; ++i) {
  36.         my_strcpy1(buf, str1);
  37.         my_strcpy1(buf, str2);
  38.     }
  39.     
  40.     return 0;
  41. }
结果如下:
  1. [xxx@xxx-vm-fc13 test]$ time ./a.out

  2. real 0m0.321s
  3. user 0m0.318s
  4. sys 0m0.003s
  5. [xxx@xxx-vm-fc13 test]$ time ./a.out

  6. real 0m0.296s
  7. user 0m0.291s
  8. sys 0m0.003s
  9. [xxx@xxx-vm-fc13 test]$ time ./a.out

  10. real 0m0.321s
  11. user 0m0.320s
  12. sys 0m0.000s
这个版本的my_strcpy1增加了一个临时变量更高效了。这有些困惑啊。这让我搞不明白为什么C库要这样实现了。即使为了保证src不变,再引入一个新的局部变量
  1. char* my_strcpy1(char *dest, const char *src)
  2. {
  3.     const char *s = src;
  4.     char *d = dest;
  5.     register char c;
  6.     
  7.     do {
  8.         c = *s++;
  9.         *d++ = c;
  10.     } while ('\0' != c);

  11.     return dest;
  12. }
测试的结果仍然优于C库的实现。


那么C库究竟为什么要用偏移的方式来设置dest的内容呢?

阅读(272) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~