程序中總難免會將字符串copy來copy去,常見的方法如:strncpy、snprintf、strlen+memmove等。(strcpy、sprintf之流就不討論了,由於容易引入目標緩衝區溢出、不能有效保證尾部/0等問題,在實際工程項目中很少使用---如果不怕被bs可以嘗試下。其他非主流方如bcopy、memccpy也不羅嗦了,華而不實,本質與上述三種方法並無區別。)
之前看過別人的總結,模糊記得在目標緩衝區較大、實際待拷貝字符串較短時,strncpy性能比較爛,後兩種性能較好,但strlen+memmove code起來略顯麻煩,所以一般推薦採用snprintf的方式進行拷貝。
偶有閒暇,本着事必躬親的精神,實際測了下,性能對比如下(程序附後):
case 1(待copy字符串與目標緩衝區長度相近):
循環次數:10000 目標緩衝區長度:10240 源字符串長度:10240
耗時: strncpy: 79ms, snprintf: 24ms, strlen+memmove:23ms
【差距明顯,但沒有數量級上的差別。】
case 2(待copy字符串與目標緩衝區相差較大):
循環次數:10000 目標緩衝區長度:10240 源字符串長度:5120
耗時: strncpy: 48ms, snprintf: 4ms, strlen+memmove:2ms
【已經產生質的差別了,耗時不在一個數量級上】
case 3(看下另一個極端,源串極小的case)
循環次數:1000000 目標緩衝區長度:10240 源字符串長度:16
耗時: strncpy: 4465ms, snprintf: 127ms, strlen+memmove:32ms
【三者已經明顯拉開距離了,一目瞭然】
從case2、case3來看,strncpy對源串長度變化不敏感,這也符合預期---源串不夠長時,strncpy會在目標緩衝區後補充0.
snprintf code起來比較簡單,效率有保證,在大多數情況下,跟strlen+memmove在一個量級上。
strlen+memmove雖然三個case都效率最好,但要多行code,而且使用strlen函數也有安全顧慮,好像很少看到這樣拷貝字符串的。
從三個case來看,無論在性能、編碼複雜度上snprintf都完勝strncpy。
因此在實際code中,strncpy往往是比較忌諱的,容易引起性能瓶頸,而且實在找不出不以snprintf替換它的理由。
順帶跑下題,程序中涉及字符串的操作部分,往往是敏感地帶,因爲稍不留神就會出現緩衝區溢出, 或處理完後目標串不能保證以/0結尾(如果真的引入這種問題,那等着在後面程序core dump、crash吧),名字帶‘n‘的函數一般可以防止目標緩衝區寫溢出,那是否也可以保證緩衝區以/0結尾呢?彙總如下(其實隨便一個講c lib庫函數地方都有說明):
strncpy: 如果src的前n個字節不含NULL字符,則結果不會以NULL字符結束。
如果src的長度小於n個字節,則以NULL填充dest直到複製完n個字節。
所以strncpy完了,儘量自己補/0
snprintf: 始終保證以/0結尾,即最多copy n-1個有效字符。
可放心用
fgets : 最多讀入n-1個字符,始終保證以/0結尾,
可放心用。
貼下性能測試code(寫的比較爛的說):
- #include "unistd.h"
- #include "sys/time.h"
- #define LOOP 10000
- #define BUF_LEN 10240
- #define SOURCE_LEN 16
- char dst[BUF_LEN];
- char src[SOURCE_LEN];
- class RecTime
- {
- public:
- RecTime()
- {
- gettimeofday(&ts, NULL);
- }
- ~RecTime()
- {
- gettimeofday(&te, NULL);
- fprintf(stdout, "time used: %dms/n",
- (te.tv_sec-ts.tv_sec)*1000 + (te.tv_usec-ts.tv_usec)/1000);
- }
- private:
- struct timeval ts,te;
- };
- void test_strncpy()
- {
- RecTime _time_record;
- for(int i=0; i<LOOP; i++)
- {
- if(dst != strncpy(dst, src, BUF_LEN))
- {
- fprintf(stderr, "fatal error, strncpy failed.");
- exit(1);
- }
- }
- return ;
- }
- void test_snprintf()
- {
- RecTime _time_record;
- for(int i=0; i<LOOP; i++)
- {
- if(SOURCE_LEN-1 != snprintf(dst, BUF_LEN, "%s", src))
- {
- fprintf(stderr, "fatal error, snprintf failed.");
- exit(1);
- }
- }
- return ;
- }
- void test_memmove()
- {
- RecTime _time_record;
- for(int i=0; i<LOOP; i++)
- {
- int len = strlen(src);
- if(dst != memmove(dst, src, len))
- {
- fprintf(stderr, "fatal error, snprintf failed.");
- exit(1);
- }
- }
- return ;
- }
- int main(int argc, char* argv[])
- {
- /**< fill src str */
- memset(src, '1', SOURCE_LEN-1);
- src[SOURCE_LEN-1]=0;
- /**< run test */
- test_strncpy();
- test_snprintf();
- test_memmove();
- return 0;
- }