strcat的幾種實現及性能比較


 

一  原型說明

     strcat()為C語言標准庫函數,用於字符串拼接。函數原型聲明在string.h頭文件中:

char *strcat(char *dest, const char *src);

     該函數將參數src所指字符串拷貝到參數dest所指字符串的結尾處(覆蓋dest結尾處的'\0')並添加'\0'。其返回值為參數dest所指字符串的起始地址。注意,dest必須有足夠的空間來容納要拷貝的src字符串。

     本文將給出strcat函數的幾種實現,並比較其執行效率。代碼運行環境如下:

 

二  代碼實現

2.1 匯編實現

 1 char *AssemStrcat(char *pszDest, const char *pszSrc)
 2 {
 3     int d0, d1, d2, d3;
 4     __asm__ __volatile__(
 5         "repne\n\t"
 6         "scasb\n\t"
 7         "decl %1\n"
 8         "1:\tlodsb\n\t"
 9         "stosb\n\t"
10         "testb %%al,%%al\n\t"
11         "jne 1b"
12         : "=&S" (d0), "=&D" (d1), "=&a" (d2), "=&c" (d3)
13         : "0" (pszSrc), "1" (pszDest), "2" (0), "3" (0xffffffff):"memory");
14     return pszDest;
15 }

2.2 模擬C庫

 1 char *SimuStrcat(char *pszDest, const char *pszSrc)
 2 {
 3     char *pszOrigDst = pszDest;
 4 
 5     while(*pszDest)
 6         pszDest++;
 7     while((*pszDest++ = *pszSrc++) != '\0')
 8         ;
 9 
10     return pszOrigDst;
11 }

     因strcat的C標准庫函數的實現方式未知,故使用SimuStrcat函數模擬標准庫實現。

2.3 快速拼接

     由strcat函數的原型說明可知,每次調用時都必須掃描整個目的字符串(以尋找結束符)。這會降低該函數的執行效率,尤其是在頻繁拼接時。

     FastStrcat函數每次返回拼接后的字符串尾部,即指向結束符所在位置。下次調用時便不再需要掃描整串,從而提高執行效率。

1 char *FastStrcat(char *pszDest, const char* pszSrc)
2 {
3     while(*pszDest)
4         pszDest++;
5     while((*pszDest++ = *pszSrc++));
6     return --pszDest;
7 }

     注意,第二個while循環若不加雙層括號則會報”suggest parentheses around assignment used as truth value”的警告。因返回時對pszDest作減法運算,故單次執行時FastStrcat慢於SimuStrcat。

     為提高執行速度,本節函數實現均未對指針參數做判空處理。若兼求安全性,可在函數內使用assert斷言指針合法性。Gcc編譯器還對函數聲明提供一種nonnull擴展屬性,可在編譯時進行有限的判空保護:

 1 char *FastStrcat(char *pszDest, const char* pszSrc)__attribute__((__nonnull__(1, 2)));
 2 char *NullPtr(void){return NULL;}
 3 int main(void)
 4 {
 5     char *pszBuf = NULL;
 6     FastStrcat(pszBuf, "Elizabeth ");
 7     FastStrcat(NullPtr(), "Elizabeth ");
8 FastStrcat(NullPtr()?pszBuf:NULL, "Elizabeth ");
9 FastStrcat(NULL, "Elizabeth "); 10 return 0; 11 }

     其中,__nonnull__(1, 2)指示編譯器對FastStrcat函數調用的第一個和第二個參數判空。

     編譯結果如下:

1 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o test test.c
2 test.c: In function 'main':
3 test.c:8: warning: null argument where non-null required (argument 1)
4 test.c:9: warning: null argument where non-null required (argument 1)

     可見,除非將指針參數顯式地置空,否則nonnull機制也檢測不到。此外,使用nonnull機制時,若打開編譯優化選項-O2,則函數內關於指針參數的校驗將被優化掉。

 

三  性能比較

     本節采用《Linux用戶態程序計時方式詳解》一文的TIME_ELAPSED()宏進行程序計時。

1 #define TIME_ELAPSED(codeToTime) do{ \
2     struct timeval beginTime, endTime; \
3     gettimeofday(&beginTime, NULL); \
4     {codeToTime;} \
5     gettimeofday(&endTime, NULL); \
6     long secTime  = endTime.tv_sec - beginTime.tv_sec; \
7     long usecTime = endTime.tv_usec - beginTime.tv_usec; \
8     printf("[%s(%d)]Elapsed Time: SecTime = %lds, UsecTime = %ldus!\n", __FILE__, __LINE__, secTime, usecTime); \
9 }while(0)

     基於該宏,編寫測試代碼如下:

 1 #ifdef strcat //檢查標准庫strcat是否用宏實現
 2     #warning strcat has been defined!
 3 #endif 
 4 int main(void)
 5 {
 6     char szCatBuf[1000];
 7     szCatBuf[0] = '\0'; //字符串快速初始化
 8 
 9     char *pszBuf = szCatBuf;
10     TIME_ELAPSED(
11         pszBuf = FastStrcat(pszBuf, "Abraham, ");
12         pszBuf = FastStrcat(pszBuf, "Alexander, ");
13         pszBuf = FastStrcat(pszBuf, "Maximilian, ");
14         pszBuf = FastStrcat(pszBuf, "Valentine ")
15     );
16     printf("  [FastStrcat]szCatBuf = %s\n", szCatBuf);
17 
18     szCatBuf[0] = '\0';
19     TIME_ELAPSED(
20         SimuStrcat(szCatBuf, "Abraham, ");
21         SimuStrcat(szCatBuf, "Alexander, ");
22         SimuStrcat(szCatBuf, "Maximilian, ");
23         SimuStrcat(szCatBuf, "Valentine ")
24     );
25     printf("  [SimuStrcat]szCatBuf = %s\n", szCatBuf);
26 
27     szCatBuf[0] = '\0';
28     TIME_ELAPSED(
29         AssemStrcat(szCatBuf, "Abraham, ");
30         AssemStrcat(szCatBuf, "Alexander, ");
31         AssemStrcat(szCatBuf, "Maximilian, ");
32         AssemStrcat(szCatBuf, "Valentine ")
33     );
34     printf("  [AssemStrcat]szCatBuf = %s\n", szCatBuf);
35 
36     szCatBuf[0] = '\0';
37     TIME_ELAPSED(
38         strcat(szCatBuf, "Abraham, ");
39         strcat(szCatBuf, "Alexander, ");
40         strcat(szCatBuf, "Maximilian, ");
41         strcat(szCatBuf, "Valentine ")
42     );
43     printf("  [LibStrcat]szCatBuf = %s\n", szCatBuf);
44 
45     szCatBuf[0] = '\0';
46     TIME_ELAPSED(
47         sprintf(szCatBuf, "%s%s%s%s","Abraham, ", "Alexander, ", "Maximilian, ", "Valentine ")
48     );
49     printf("  [LibSprintf]szCatBuf = %s\n", szCatBuf);
50 
51     return 0;
52 }

     除上節三種strcat實現外,代碼還對齊庫實現及sprintf方式進行測試。測試結果如下:

 1 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o test test.c
 2 [wangxiaoyuan_@localhost test1]$ ./test
 3 [test.c(15)]Elapsed Time: SecTime = 0s, UsecTime = 2us!
 4   [FastStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 5 [test.c(24)]Elapsed Time: SecTime = 0s, UsecTime = 2us!
 6   [SimuStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 7 [test.c(33)]Elapsed Time: SecTime = 0s, UsecTime = 1us!
 8   [AssemStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 9 [test.c(42)]Elapsed Time: SecTime = 0s, UsecTime = 1us!
10   [LibStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
11 [test.c(48)]Elapsed Time: SecTime = 0s, UsecTime = 6us!
12   [LibSprintf]szCatBuf = Abraham, Alexander, Maximilian, Valentine

     因每次測試僅調用一次待測函數,故計時不太精准。對比另一次測試結果:

 1 [wangxiaoyuan_@localhost test1]$ ./test
 2 [test.c(15)]Elapsed Time: SecTime = 0s, UsecTime = 4us!
 3   [FastStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 4 [test.c(24)]Elapsed Time: SecTime = 0s, UsecTime = 4us!
 5   [SimuStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 6 [test.c(33)]Elapsed Time: SecTime = 0s, UsecTime = 3us!
 7   [AssemStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
 8 [test.c(42)]Elapsed Time: SecTime = 0s, UsecTime = 2us!
 9   [LibStrcat]szCatBuf = Abraham, Alexander, Maximilian, Valentine 
10 [test.c(48)]Elapsed Time: SecTime = 0s, UsecTime = 15us!
11   [LibSprintf]szCatBuf = Abraham, Alexander, Maximilian, Valentine

     可見,單次執行時,strcat庫函數最快,FastStrcat居中,sprintf庫函數最慢。

     接着,比較批量執行時strcat庫函數和FastStrcat的速度。測試代碼如下:

 1 int main(void)
 2 {
 3 #define ROUND    (unsigned short)1000
 4     char szCatBuf[sizeof("Elizabeth ")*ROUND];
 5     szCatBuf[0] = '\0';
 6 
 7     char *pszBuf = szCatBuf;
 8     TIME_ELAPSED(
 9         unsigned short wIdx = 0;
10         for(; wIdx < ROUND; wIdx++)
11             pszBuf = FastStrcat(pszBuf, "Elizabeth ");
12     );
13 
14     szCatBuf[0] = '\0';
15     TIME_ELAPSED(
16         unsigned short wIdx = 0;
17         for(; wIdx < ROUND; wIdx++)
18             strcat(szCatBuf, "Elizabeth ");
19     );
20 
21     return 0;
22 }

     測試結果如下:

1 [wangxiaoyuan_@localhost test1]$ gcc -Wall -o test test.c
2 [wangxiaoyuan_@localhost test1]$ ./test
3 [test.c(12)]Elapsed Time: SecTime = 0s, UsecTime = 99us!
4 [test.c(19)]Elapsed Time: SecTime = 0s, UsecTime = 3834us!

     可見,批量執行時,FastStrcat遠快於strcat庫函數。

 

四  總結

     單次拼接時,strcat庫函數最快,FastStrcat居中,sprintf庫函數最慢(若非格式化需要不建議用於字符串拼接)。

     頻繁拼接時,FastStrcat相比strcat庫函數具有明顯的速度優勢。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM