[源码分析] 【glibC阅读】之 strlen实现

辛昕   2018-1-8 00:18 楼主
写在前面的话: 其实不管是 strlen的实现,甚至是glibc的实现,对我来说其实都不是什么有多大实际价值的事情。我只是意识到,如果我真的要学会阅读代码,我必须真的去尝试阅读代码。 而选择glibc,是因为它是一个通用的标准库,而且与我常年使用C有密切关系。 这个帖子的内容,对于那些早已经熟悉这些套路的人(x86汇编和gcc大神)来说,简直小儿科到不足一提。 不过,我相信对于大多数和我这样,对此几乎一窍不通的小白来说,这个过程本身是很有参考价值的。而我写下这个过程,本身也是为我逐渐积累阅读较大型代码库的能力和经验做准备。希望没有破坏什么人的雅兴和胃口。 此前发的帖子,那个strlen的glibc实现分析,太监了很久。周末用了所剩不多的零碎时间,尝试阅读一下。 当然现在也还是不十分明白,但是,先做个记录。 环境说明: 1.我用的glibC 源码版本是 2.14 2.我用的souce insight阅读——否则我接下来叙述的内容,有可能出现部分名词不一致,可能影响理解。 阅读方式: 由于我仍然未能理解glibc的项目源码结构,所以我尚不能跑起任何一个小程序exe,我仅仅从代码的依赖关系上去分析。 我的分析起点正是 对 strlen() 邮件 go to definition...... 本帖最后由 辛昕 于 2018-1-8 00:43 编辑
强者为尊,弱者,死无葬身之地

回复评论 (6)

找下来,总共有四处定义——我不得不说,对于第一次阅读比较大的库的我来说,这是个下马威。

硬着头皮看,其中两处为 .c源文件,两处为.h头文件,以至于我以为两个是宏定义,但后来看并非如此,而是 内联函数。

内联函数在cpp里我见得比较多,但在C里实际上见的不多,因为很多时候,IAR也好,MDK也好,都无法使用。

闲话少说,以下四楼,分别列出。
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:24
// 我根据自己喜欢的代码格式,做了一些很细微的调整,但未动一个字母。
  1. // 1 strlen - Function in Strlen.c (sysdeps\i386) at line 23 (13 lines)
  2. #include <string.h>
  3. size_t strlen (const char *str)
  4. {
  5. int cnt;
  6. asm( "cld\n" /* Search forward. */
  7. /* Some old versions of gas need `repne' instead of `repnz'. */
  8. "repnz\n" /* Look for a zero byte. */
  9. "scasb" /* %0, %1, %3 */ :
  10. "=c" (cnt) : "D" (str), "0" (-1), "a" (0));
  11. return -2 - cnt;
  12. }
  13. libc_hidden_builtin_def (strlen)
  14. // 这段代码,显然是针对x86的实现,一堆asm,鬼看得懂,也不在意,在意的是 最后一句,是什么意思
  15. // 同时好奇的是,那其他几个实现是怎么回事
本帖最后由 辛昕 于 2018-1-8 00:26 编辑
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:25
  1. // *这个文件位于 根目录下 string/strlen.c
  2. //2 strlen - Function in Strlen.c (string) at line 29 (78 lines)
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #undef strlen // 标注1
  6. /* Return the length of the null-terminated string STR. Scan for
  7. the null terminator quickly by testing four bytes at a time. */
  8. size_t strlen (str) const char *str;
  9. {
  10. const char *char_ptr;
  11. const unsigned long int *longword_ptr;
  12. unsigned long int longword, himagic, lomagic;
  13. /* Handle the first few characters by reading one character at a time.
  14. Do this until CHAR_PTR is aligned on a longword boundary. */
  15. for (char_ptr = str; ((unsigned long int) char_ptr
  16. & (sizeof (longword) - 1)) != 0;
  17. ++char_ptr)
  18. if (*char_ptr == '\0')
  19. return char_ptr - str;
  20. /* All these elucidatory comments refer to 4-byte longwords,
  21. but the theory applies equally well to 8-byte longwords. */
  22. longword_ptr = (unsigned long int *) char_ptr;
  23. /* Bits 31, 24, 16, and 8 of this number are zero. Call these bits
  24. the "holes." Note that there is a hole just to the left of
  25. each byte, with an extra at the end:
  26. bits: 01111110 11111110 11111110 11111111
  27. bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD
  28. The 1-bits make sure that carries propagate to the next 0-bit.
  29. The 0-bits provide holes for carries to fall into. */
  30. himagic = 0x80808080L;
  31. lomagic = 0x01010101L;
  32. if (sizeof (longword) > 4)
  33. {
  34. /* 64-bit version of the magic. */
  35. /* Do the shift in two steps to avoid a warning if long has 32 bits. */
  36. himagic = ((himagic << 16) << 16) | himagic;
  37. lomagic = ((lomagic << 16) << 16) | lomagic;
  38. }
  39. if (sizeof (longword) > 8)
  40. abort ();
  41. /* Instead of the traditional loop which tests each character,
  42. we will test a longword at a time. The tricky part is testing
  43. if *any of the four* bytes in the longword in question are zero. */
  44. for (;;)
  45. {
  46. longword = *longword_ptr++;
  47. if (((longword - lomagic) & ~longword & himagic) != 0)
  48. {
  49. /* Which of the bytes was the zero? If none of them were, it was
  50. a misfire; continue the search. */
  51. const char *cp = (const char *) (longword_ptr - 1);
  52. if (cp[0] == 0)
  53. return cp - str;
  54. if (cp[1] == 0)
  55. return cp - str + 1;
  56. if (cp[2] == 0)
  57. return cp - str + 2;
  58. if (cp[3] == 0)
  59. return cp - str + 3;
  60. if (sizeof (longword) > 4)
  61. {
  62. if (cp[4] == 0)
  63. return cp - str + 4;
  64. if (cp[5] == 0)
  65. return cp - str + 5;
  66. if (cp[6] == 0)
  67. return cp - str + 6;
  68. if (cp[7] == 0)
  69. return cp - str + 7;
  70. }
  71. }
  72. }
  73. }
  74. libc_hidden_builtin_def (strlen)
  75. // 这是一段看起来完全与平台无关的代码。似乎可以认为是我们追踪的终点。
  76. // 我唯一有点好奇,相信也是你们好奇的,为什么,我们可以用区区几行代码实现的 strlen() 在这里居然这么复杂!
  77. // 另外,见标注1:这是在干什么?
  78. // 它要取消前面的(宏)定义 strlen,所以几乎可以料想,还有两个 strlen 的 宏定义(不要被小写迷惑了?)
本帖最后由 辛昕 于 2018-1-8 00:31 编辑
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:27
//3 strlen - Macro in String.h (sysdeps\s390\bits) at line 43 // 这段预警告似乎在暗示我们,这个定义是一个替代定义。
  1. #ifndef _STRING_H
  2. # error "Never use <bits/string.h> directly; include <string.h> instead."
  3. #endif
// 紧随其后的是一个 strlen 的实现版本
  1. #ifndef _FORCE_INLINES
  2. #define strlen(str) __strlen_g ((str))
  3. __STRING_INLINE size_t __strlen_g (__const char *) __asm__ ("strlen");
  4. // 这段代码的解释:
  5. // __STRING_INLINE 这个没什么,搜索可以看到,它可能有三种定义:1.啥都没;2.inline;3.extern inline
  6. // 虽然我并不理解C下的 内联函数是怎么回事,但内联就只是内联而已,没什么特别的。
  7. // 至于 extern inline 我的确并不确切理解这个 外部是个什么鬼,但不管如何,也就只是个内联,在
  8. // 这次的阅读里,我真心丝毫不关心。
  1. // __asm__ 倒是一个知识点,不过,也没什么特别,它是gcc的一个关键字,意思是接下来要使用汇编代码了。
  2. // 我还是不明白的是, 后面跟着一个 ("strlen") 这是个什么操作?
  3. // 百度到一个这样的东西,让我领悟到 这个 内嵌 asm 的语法
  4. /*
  5. __asm__("mov r0, #0\n"}
  6. */
  7. // 所以上述的 这句话,其实大概率翻译成常见的形式就会是
  8. /*
  9. inline size_t __strlen_g (__const char *)
  10. {
  11. __asm__ ("strlen");
  12. }
  13. 我并不明白这个地方为毛这么该死非要挤在一行上看,但显然,至少对于我,这样的格式我更容易看懂
  14. 我可以理解在汇编里出现 mov 之类的语句,
  15. 但出现一个 strlen 我是理解不了的。
  16. */
  1. __STRING_INLINE size_t __strlen_g (__const char *__str)
  2. {
  3. char *__ptr, *__tmp;
  4. __ptr = (char *) 0;
  5. __tmp = (char *) __str;
  6. __asm__ __volatile__ (" la 0,0\n"
  7. "0: srst %0,%1\n"
  8. " jo 0b\n"
  9. : "+&a" (__ptr), "+&a" (__tmp) :
  10. : "cc", "memory", "0" );
  11. return (size_t) (__ptr - __str);
  12. }
  13. // 这一段对我来说倒没什么太特别的,无非就是一堆x86汇编代码,我看不懂,但反正就是在干strlen该干的事的意思
  14. #endif
本帖最后由 辛昕 于 2018-1-8 00:35 编辑
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:30
  1. // 先看最后一处 strlen 宏定义
  2. //4 strlen - Macro in String.h (sysdeps\i386\i486\bits) at line 549 (4 lines)
  3. /* Return the length of S.  */
  4. #define _HAVE_STRING_ARCH_strlen 1
  5. #define strlen(str) \
  6.   (__extension__ (__builtin_constant_p (str)                                      \
  7.                   ? __builtin_strlen (str)                                      \
  8.                   : __strlen_g (str)))
  9.                   
  10.                         // 标注2
  11. __STRING_INLINE size_t __strlen_g (__const char *__str);

  12. __STRING_INLINE size_t
  13. __strlen_g (__const char *__str)
  14. {
  15.   register char __dummy;
  16.   register __const char *__tmp = __str;
  17.   __asm__ __volatile__
  18.     ("1:\n\t"
  19.      "movb        (%0),%b1\n\t"
  20.      "leal        1(%0),%0\n\t"
  21.      "testb        %b1,%b1\n\t"
  22.      "jne        1b"
  23.      : "=r" (__tmp), "=&q" (__dummy)
  24.      : "0" (__str),
  25.        "m" ( *(struct { char __x[0xfffffff]; } *)__str)
  26.      : "cc" );
  27.   return __tmp - __str - 1;
  28. }

  29. /*
  30.         总体而言,这是一个和 第三处,其实十分相似的结构。
  31.         唯一要理解的是一个新的语法团——标注2
  32.        
  33. */
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:51
我最终意识到,要真的继续玩下去。
除了去挖那些很可能是 x86汇编语法 或者是 gcc 语法。

否则,真的不知道这一个小小的strlen都能给我闹出4个定义,我鬼知道你最后到底用的是哪个定义啊?
彼此又是什么关系,又为毛要搞得这么复杂?

这些都是接下去要尝试做的事情。
强者为尊,弱者,死无葬身之地
点赞  2018-1-8 00:52
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复