字符串copy效率大比拼

  程序中总难免会将字符串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(写的比较烂的说):

[cpp]  view plain copy
  1. #include "unistd.h"  
  2. #include "sys/time.h"  
  3.   
  4. #define LOOP  10000  
  5. #define BUF_LEN 10240  
  6. #define SOURCE_LEN 16  
  7. char dst[BUF_LEN];  
  8. char src[SOURCE_LEN];  
  9.   
  10. class RecTime  
  11. {  
  12.     public:  
  13.         RecTime()  
  14.         {     
  15.             gettimeofday(&ts, NULL);  
  16.         }     
  17.         ~RecTime()  
  18.         {     
  19.             gettimeofday(&te, NULL);  
  20.             fprintf(stdout, "time used: %dms/n",   
  21.                     (te.tv_sec-ts.tv_sec)*1000 + (te.tv_usec-ts.tv_usec)/1000);  
  22.         }     
  23.     private:  
  24.         struct timeval ts,te;  
  25. };  
  26.   
  27. void test_strncpy()  
  28. {  
  29.     RecTime _time_record;  
  30.     for(int i=0; i<LOOP; i++)  
  31.     {     
  32.         if(dst != strncpy(dst, src, BUF_LEN))  
  33.         {     
  34.             fprintf(stderr, "fatal error, strncpy failed.");  
  35.             exit(1);  
  36.         }     
  37.     }     
  38.     return ;  
  39. }  
  40.   
  41. void test_snprintf()  
  42. {  
  43.     RecTime _time_record;  
  44.     for(int i=0; i<LOOP; i++)  
  45.     {  
  46.         if(SOURCE_LEN-1 != snprintf(dst, BUF_LEN, "%s", src))  
  47.         {  
  48.             fprintf(stderr, "fatal error, snprintf failed.");  
  49.             exit(1);  
  50.         }  
  51.     }  
  52.     return ;  
  53. }  
  54.   
  55. void test_memmove()  
  56. {  
  57.     RecTime _time_record;  
  58.     for(int i=0; i<LOOP; i++)  
  59.     {  
  60.         int len = strlen(src);  
  61.         if(dst != memmove(dst, src, len))  
  62.         {  
  63.             fprintf(stderr, "fatal error, snprintf failed.");  
  64.             exit(1);  
  65.         }  
  66.     }  
  67.     return ;  
  68. }  
  69.   
  70.   
  71.   
  72. int main(int argc, char* argv[])  
  73. {  
  74.     /**< fill src str               */  
  75.     memset(src, '1', SOURCE_LEN-1);  
  76.     src[SOURCE_LEN-1]=0;  
  77.     /**< run test               */  
  78.     test_strncpy();  
  79.     test_snprintf();  
  80.     test_memmove();  
  81.     return 0;  
  82. }  


### C语言字符串处理常见操作及其示例 #### 1. 字符串拷贝 C语言中的字符串拷贝可以通过 `strcpy` 或更安全的 `strcpy_s` 函数完成。这些函数用于将源字符串的内容复制到目标字符串中。 ```c #include <stdio.h> #include <string.h> int main() { char source[] = "Hello, world!"; char destination[50]; strcpy(destination, source); // 复制source到destination printf("Copied string: %s\n", destination); return 0; } ``` 上述代码展示了如何使用 `strcpy` 进行简单的字符串拷贝[^1]。 #### 2. 安全的字符串拷贝 为了防止缓冲区溢出,推荐使用带有边界控制的安全版本 `strcpy_s`: ```c #include <stdio.h> #include <string.h> int main() { char source[] = "Safe copy example"; char destination[50]; strcpy_s(destination, sizeof(destination), source); printf("Safely copied string: %s\n", destination); return 0; } ``` 此代码片段说明了如何通过指定最大长度来避免潜在的风险。 #### 3. 字符输入与读取 对于单个字符的输入,可以利用标准库函数 `getc()` 实现交互式数据获取。 ```c #include <stdio.h> int main() { char inputChar; printf("Enter a character: "); inputChar = getc(stdin); // 获取用户输入的一个字符 printf("You entered: %c\n", inputChar); return 0; } ``` 这里演示了一个基本的例子,展示如何接收并打印用户的键盘输入[^2]。 #### 4. 整行字符串读入 当需要一次性捕获多于一个字节的数据流时,可采用 `gets()` 方法(注意其不安全性),或者优选替代方案如 `fgets()` 来代替它以增强程序健壮性。 ```c #include <stdio.h> int main() { char buffer[100]; printf("Type something:\n"); fgets(buffer, sizeof(buffer), stdin); // 使用fgets而非过时且危险的gets() printf("Input was: %s", buffer); return 0; } ``` 这段脚本解释了为什么应该优先考虑更加可靠的选项而不是传统的但存在隐患的方式。 #### 5. 部分字符串比较 有时仅需对比两段文字前若干位是否一致即可满足需求,在这种情况下调用 `strncmp` 是理想的选择因为它允许设定限定范围内的核查数量。 ```c #include <stdio.h> #include <string.h> int main(){ const char* s1="abcdefg"; const char* s2="abcxyz"; if(strncmp(s1,s2,3)==0){ puts("First three characters match."); } else{ puts("Strings differ within first three chars."); } return 0; } ``` 以上实例验证了两个序列开头部分的一致程度,并依据结果给出相应反馈[^3]。 #### 6. 字符串连接 要将多个子串组合成一个新的整体表达形式,则需要用到诸如 `strcat` 及带参数约束版次级功能——即 `strncat` ——来进行拼接动作。 ```c #include<stdio.h> #include<string.h> int main(){ char dest[50]="This is "; strcat(dest,"a test."); printf("%s\n",dest); memset(dest,'\0',sizeof(dest)); strncpy(dest,"Another ",8); strncat(dest,"example.",7); printf("%s\n",dest); return 0; } ``` 该示范阐述了怎样运用基础以及高级别的串联技巧构建复合型表述结构[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值