GNU版strlen源碼分析

來源:互聯網
上載者:User

直接操作C標準庫提供的字串操作函數是有一定風險的,稍有不慎就會導致記憶體問題。這周用業餘時間寫了一個小型的安全字串操作庫,但是測試之後才發現自己的實現有很大的效能缺陷。 
在Solaris上初步做了一個簡單的效能比對,以下是得到的效能資料(以strlen的資料為例): 
當傳入的字串長度為10時,執行100w次: 
strlen 執行時間是:32762毫秒 
my_strlen執行時間是:491836毫秒 
當傳入的字串長度為20時,執行100w次: 
strlen 執行時間是:35075毫秒 
my_strlen執行時間是:770397毫秒 
很顯然,標準庫中strlen的消耗僅是my_strlen的十分之一不到,且其效能消耗隨著字串長度的增加並未有近線性增加,而my_strlen則是變化明顯。想必大家這時也能猜到my_strlen採用了傳統的實現的方式,即採用逐個位元組判斷是否為'/0'方式,這也與測試出的現象相符。本著刨根問底的精神,我在網上找到了GNU提供的C標準庫中strlen實現的源碼,要看看GLIBC中strlen究竟採用何種技巧才達到了那麼高的效能。說實話在效能最佳化這方面自己一直還處於比較初級的位置,這也將是自己將來努力的一個方向。 
下載了全部GLIBC的程式碼封裝,這個包還真不小。在string子目錄下找到strlen.c,這就是大多數UNIX平台、Linux平台以及絕大多數GNU軟體使用的strlen的實現源碼了。這份代碼由Torbjorn Granlund(還實現了memcpy)編寫,Jim Blandy和Dan Sahlin提供了協助和注釋。包括注釋在內,GLIBC的strlen的代碼足足有近130行,大致瀏覽一下, 沒有怎麼看懂,可耐下心來細緻閱讀,還是有些心得的。下面是strlen源碼摘要版,後面我將針對這段代碼寫一些我的理解: 
  1 /* Return the length of the null-terminated string STR.  Scan for 
  2    the null terminator quickly by testing four bytes at a time.  */ 
  3 size_t strlen (str)  const char *str; 
  4 { 
  5         const char *char_ptr; 
  6         const unsigned long int *longword_ptr; 
  7         unsigned long int longword, magic_bits, himagic, lomagic; 
  8 
  9         /* Handle the first few characters by reading one character at a time. 
10            Do this until CHAR_PTR is aligned on a longword boundary.  */ 
11 
12         for (char_ptr = str; ((unsigned long int) char_ptr 
13              & (sizeof (longword) - 1)) != 0; 
14              ++char_ptr) 
15                 if (*char_ptr == '/0') 
16                         return char_ptr - str; 
17 
18         /* All these elucidatory comments refer to 4-byte longwords, 
19            but the theory applies equally well to 8-byte longwords.  */ 
20 
21         longword_ptr = (unsigned long int *) char_ptr; 
22 
23         himagic = 0x80808080L; 
24         lomagic = 0x01010101L; 
25 
26         if (sizeof (longword) > 8) 
27                 abort (); 
28 
29         /* Instead of the traditional loop which tests each character, 
30            we will test a longword at a time.  The tricky part is testing 
31            if *any of the four* bytes in the longword in question are zero.  */ 
32 
33         for (;;)      
34         {                         
35                 longword = *longword_ptr++;     
36 
37                 if ( ((longword - lomagic) & himagic) != 0) 
38                 { 
39                         /* Which of the bytes was the zero?  If none of them were, it was 
40                            a misfire; continue the search.  */ 
41 
42                         const char *cp = (const char *) (longword_ptr - 1); 
43 
44                         if (cp[0] == 0) 
45                                 return cp - str; 
46                         if (cp[1] == 0) 
47                                 return cp - str + 1; 
48                         if (cp[2] == 0) 
49                                 return cp - str + 2; 
50                         if (cp[3] == 0) 
51                                 return cp - str + 3; 
52                         if (sizeof (longword) > 4) 
53                         { 
54                                 if (cp[4] == 0) 
55                                         return cp - str + 4; 
56                                 if (cp[5] == 0) 
57                                         return cp - str + 5; 
58                                 if (cp[6] == 0) 
59                                         return cp - str + 6; 
60                                 if (cp[7] == 0) 
61                                         return cp - str + 7; 
62                         } 
63                 } 
64         } 
65 } 
從這段代碼開頭作者的注釋我們大致可以瞭解到該strlen實現的原理:就是通過每次測試四個位元組來代替傳統實現中每次測試一個位元組的方法。知道這個原理了,那麼還需要解決兩個難題: 
1) C標準庫要求有很好的移植性,在絕大部分系統體繫結構下都應該能正確運行。那麼每次拿出4個位元組比較(unsigned long int),就需要考慮記憶體對齊問題,傳入的字串的首字元地址可不一定在4對齊的地址上; 
2) 如何對四個位元組進行測試,找出其中某個位元組為全0,這是個技巧問題。 
12~21行的代碼解決的就是第一個問題: 
      for (char_ptr = str; ((unsigned long int) char_ptr 
             & (sizeof (longword) - 1)) != 0; 
             ++char_ptr) 
                if (*char_ptr == '/0') 
                        return char_ptr - str; 
        /* All these elucidatory comments refer to 4-byte longwords, 
           but the theory applies equally well to 8-byte longwords.  */ 
        longword_ptr = (unsigned long int *) char_ptr; 
作者通過一個for-loop找到傳入字串中第一個地址對齊到4的字元的地址,由於該地址已經對齊到4,所以最後一行那個強制轉型是安全的。雖然可以通過圓整算式直接得到該對齊地址,但是考慮到這個區間可能存在的'/0',一個字元一個字元比對也是不可避免的。在很多嚴格對齊的架構上(比如SUN的SPARC平台),編譯器一般會將字串地址在編譯器就放到對齊的地址上,這樣一來,實際執行strlen時for-loop很少能執行一步。 
第二個問題作者則是通過一個"帶前提"的技巧來解決的。作者設定了兩個掩碼變數: 
himagic = 0x80808080L; 
lomagic = 0x01010101L; 
並通過一個conditional expression完成了對四位元組中全0位元組的檢測:((longword - lomagic) & himagic) != 0 
我們將himagic和lomagic按bit展開: 
himagic   1000 0000 1000 0000 1000 0000 1000 0000 
lomagic   0000 0001 0000 0001 0000 0001 0000 0001 
對於這樣的代碼,似乎沒有什麼理論可以遵循,需要在實踐中去理解。起初我構造了一個不含全0位元組的longword,比如: 
longword  1000 0001 1000 0001 1000 0001 1000 0001,然後按照那個條件運算式計算後,居然也滿足!=0的條件,是不是作者的邏輯有問題呢?後來轉念一想,這種邏輯是有“前提條件”的。回顧一下strlen是做什麼的,其輸入參數是任意的嗎?當然不是。輸入的字串中每個字元的值都在[0, 127]的ascii碼範圍內,也就是說每個位元組最高位的bit都是0,這樣longword就應該是如下這個樣子了: 
longword  0xxx xxxx 0xxx xxxx 0xxx xxxx 0xxx xxxx 
基於這樣的前提我們考慮兩種情況: 
當longword中沒有全0位元組時,比如: 
longword 0000 0001 0000 0001 0000 0001 0000 0001 
這樣在做完計算後,值為0,不滿足條件。 
當longword中有全零位元組時,比如: 
longword 0000 0000 0000 0001 0000 0001 0000 0001 
這樣在做完計算後,最高位元組最高bit的值肯定為1,滿足!=0條件,全0位元組被檢測出來。也就是說一旦有全0位元組,在減去lomagic時勢必會產生借位,全0的那個位元組在減去lomagic後最高位bit肯定由0變1,這樣與himagic一與,肯定不為0,就是這麼檢測出來的。 
這一方法在64位平台依然適用,上面的代碼摘要中省略了對64bit平台的特殊處理,為的是使代碼邏輯更清晰,更易讀。

 

轉自:http://blog.csdn.net/hashmat/article/details/6054046

拓展:http://blog.csdn.net/dog250/article/details/5302947

        http://blog.csdn.net/dog250/article/details/5302948

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.