一 原型說明
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庫函數具有明顯的速度優勢。