memcpy

来源:互联网 发布:什么桌面软件好 编辑:程序博客网 时间:2024/06/11 17:47

git拷贝、比较函数使用小结

----吴海生167152

 

在编码的过程中,拷贝、比较是不可避免的操作。目前我们可以使用的拷贝、比较函数比较多,如memcpy、strcpy、strncpy、sprintf、strncmp、memcmp等,它们有相同的地方,也有一些区别,之前rsvp在使用snprintf(封装成XOS_snprintf)就出现过一个小故障。那么在使用它们的时候有什么需要注意的地方呢,下面做一个简单的对比,大家有什么想法可以进行补充(以下源码来自linux内核实现)。

1、memcpy

这个函数大家一般都比较熟悉,不仅可以针对字符串,也可以针对其他类型的内存进行考本操作。

原型如下:

void* memcpy(void* dest, const void*src, unsigned n);

 

函数实现如下:

void* memcpy(void* dest, const void* src, unsigned n)

{

       int i;

       char *d = (char *)dest,*s = (char *)src;

 

       for (i=0;i<n;i++)d[i] = s[i];

       return dest;

}

从上面的代码实现来看,memcpy是没有向目的内存中加入结束符\0的,如果是字符串操作,则需用户手动保证memcpy拷贝结束后添加一个\0。

优点:可以适用于任何数据类型的拷贝,且有长度的限制;

缺点:对于字符串操作时,需手工添加\0结束符。

 

2、strcpy

最常用的字符串拷贝函数。

原型如下:

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

 

实现如下:

char * strcpy(char * dest,const char *src)

{

       char *tmp = dest;

 

       while ((*dest++ =*src++) != '\0')

              /* nothing */;

       return tmp;

}

strcpy函数是以\0为判断结束的,如果src不是\0结束的,那就可能会造成越界了。另外,如果src的长度大于dest内存的长度,那么也有可能会造成越界,所以strcpy函数是比较危险的一个函数,使用时需谨慎。

优点:实现简单,使用方便

缺点:其实优点有时也是缺点的根源,呵呵。 没有长度的判断,且是以\0为结束标志,使用不当容易造成越界;

3、strncpy

strncpy和strcpy是一类的函数,多了一个长度的入参。

 

函数原型如下:

char * strncpy(char * dest, const char *src, size_t count);

 

函数实现如下:

char * strncpy(char * dest, const char *src, size_t count)

{

       char *tmp = dest;

 

       while (count) {

              if ((*tmp = *src)!= 0) src++;

              tmp++;

              count--;

       }

       return dest;

}

 

从代码实现上来看,不管源内存有多长,stncpy都会从源内存中拷贝n个字符到目的内存中。(这n个字符中可能存在多个\0,即只要一遇到\0,则后面拷贝的字符都是\0),见如下调试程序:

#include <stdio.h>

#include <string.h>

 

int main()

{

    char name[5] ={'a','b','\0','c','d'};

 

    char temp[5] = {'\0'};

 

    strncpy(temp,name,5);

 

    printf("%c,\n%c,\n%c,\n%c,\n%c,\n!!!\n",temp[0],temp[1],temp[2],temp[3],temp[4]);

}

 

执行结果如下:

[root@localhost home]# ./a.out

a,

b,

,

,

,

!!!

 

优点:通过添加了长度这个入参,能够对拷贝的字符串进行一定的控制,越界的可能性大大降低。

缺点:strncpy执行完后,目的内存中还是不一定会以\0结束,因为strncpy没有自动的加入结束符\0,所以目的内存赋值前需要初始化下的。

4、sprintf和snprintf

sprintf ,snprintf是将字串格式化的一类函数。

函数原型:

int sprintf(char* buf, const char *fmt, ...)

 

int snprintf(char* buf, size_t size, const char *fmt, ...)

 

函数实现

int sprintf(char* buf, const char *fmt, ...)

{

       va_list args;

       int i;

 

       va_start(args, fmt);

       i=vsprintf(buf,fmt,args);   -----实现比较长

       va_end(args);

       return i;

}

 

 

int snprintf(char* buf, size_t size, const char *fmt, ...)

{

       va_list args;

       int i;

 

       va_start(args, fmt);

       i=vsnprintf(buf,size,fmt,args);-----实现比较长

       va_end(args);

       return i;

}

 

sprintf会根据format字符串来格式化数据,然后将结果复制到buf中去,直到出现结束符\0为止,这个和strcpy有点类似,使用时需注意缓存溢出。

snprintf和sprintf的作用类似,只不过多了一个长度的控制。snprintf的一个使用注意的地方就是拷贝n个字符中是包含一个结束符\0的,之前因为使用这个函数而导致了一个show命令显示的故障,因为最后一个字符被\0截取了。见linux下的帮助说明:

Upon  successful return, these functions return thenumber of characters printed (not including the trailing '\0' used to endoutput to strings).  The functionssnprintf() and vsnprintf() do not write more than size bytes (including thetrail-ing  '\0').

 

5、strcat和strncat

对两个字符串进行拼接的操作函数。

函数原型:

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

 

char * strncat(char *dest, const char *src, size_t count);

 

函数实现如下:

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

{

       char*tmp = dest;

 

       while (*dest)

              dest++;

       while ((*dest++ = *src++)!= '\0')

              ;

 

       return tmp;

}

 

 

char * strncat(char *dest, const char *src, size_t count)

{

       char *tmp = dest;

 

       if (count) {

              while (*dest)

                     dest++;

              while ((*dest++ =*src++) != 0) {

                     if(--count == 0) {

                            *dest = '\0';

                            break;

                     }

              }

       }

 

       return tmp;

}

 

strcat的函数实现上和strcpy是类似的,是以\0为循环结束标识的,所以很容易造成越界的访问。

strncat函数由于加了长度的限制,使用起来会安全些。另外需要注意的是,strncat在目的内存的末尾加入了结束符\0,因此最多拷贝了n+1个字符到目的内存中去。这个和strncpy不一样(strncpy只拷贝n个字符,不加结束符\0),和snprintf也不一致 (snprintf拷贝n个字符,包括一个\0结束符)。这些实现的不一致给我们带来了使用的很不方便,所以感觉统一标准还是比较重要的。

6、strncmp和memcmp

   strncmp用来比较两个字符串是否相同的,memcmp除了可以用于任何数据类型的比较。

函数原型:

intstrncmp(const char * cs,const char * ct,size_t count)

 

intmemcmp(const void * cs,const void * ct,size_t count);

 

函数实现:

intstrncmp(const char * cs,const char * ct,size_t count)

{

       register signed char __res = 0;

 

       while (count) {

              if ((__res = *cs - *ct++) != 0 || !*cs++)

                     break;

              count--;

       }

 

       return __res;

}

 

intmemcmp(const void * cs,const void * ct,size_t count)

{

       constunsigned char *su1, *su2;

       intres = 0;

 

       for(su1 = cs, su2 = ct; 0 < count; ++su1, ++su2,count--)

              if((res = *su1 - *su2) != 0)

                     break;

       return res;

}

 

从上述代码实现上来看,strncmp是有两个循环结束条件的,一个是循环次数n,一个是字符串结束标识符;memcmp是只有一个循环结束条件,即循环次数n,即memcmp会比较n个字节的。所以在用memcmp对字符串进行比较时还是需要谨慎的

 

7、小结

   以上只是列举了在我们编码过程中所经常使用到的几个拷贝、比较函数。从中可以看到实现上还是有一些细微的差别的,如果在编码中没有注意到这些细节,可能会造成一些或大或小的问题。

    一般来说,有长度限制的函数要安全一些,不过每个函数也有各自的特点,需要参考上下文来恰当使用。

本文仅列了一些常见的函数,欢迎大家进行补充。

 

之前写编程规范的时候,在 1.8 节里提过。现在单独抽取出来这一节,再补充两句(下文蓝色部分),供参考。

 

关于 XOS_STRNCPY 与strncpy 的不同点:

1、N 不同,XOS_STRNCPY里面的 N 包含 '\0' 结束符。strncpy 里面的 n 不会自动包含'\0' 结束符。

2、返回值不同,XOS_STRNCPY返回拷贝的字节数,strncpy 返回 dst 字符串指针。

 

用法区别上:

1、XOS_STRNCPY不会导致越界。有可能导致字符串被截断,为了避免这个问题,需要保证 dst 的缓存长度比 src 的字符串长度长至少一个字节即可避免这个问题。

2、strncpy不会被截断,会一直拷贝到 n 个字符为止。但是有可能越界,也有可能是因为 dst 的容量比 src 的长度长很多导致效率问题。为了避免这个问题,并兼顾效率,可以这样写:

   charbuf[RESERVED];

   int len =(RESERVED - 1) <= strlen(src) ? (RESERVED - 1) : strlen(src);

  memcpy(buf,src,len);

   buf[len] = 0;

   一般情况,如果 dst是传递过来的参数,同时还传递了一个长度 n 过来,那只能拷贝 n 个长度,忽略效率问题了。

 

   strncpy 便于链式操作。

 

           

1.8.1、字符串操作:n系列操作,★★★

具体要求:

进行字符串操作时,必须使用n系列函数,而不能使用老的非安全函数,如

STRNCPY(建议使用)   strcpy(禁止使用)

STRNCMP(建议使用)   strcmp(禁止使用)

……

原因说明:

使用非n系列字符串函数极容易造成内存越界等难以定位的故障。

附录:常用n系列字符串函数

另外,将非对外接口放在头文件中,其他模块可能会引用该非对外接口,一旦被别人使用,你就必须一直维护该接口(事实上的对外接口),对扩展性和可维护性带来冲击。

#define STRNCAT(dst, src, n)            strncat(dst,src,n);

要点:把字符串src的前n个字符长度连接在dst的后面,并且添加结束符\0,所以最多会有n+1个字符长度被复制

dst必须有足够的空间

返回:返回dst

 

#define STRNCPY(dst, src,n)           strncpy(dst, src,n)

要点:把字符串src的前n个字符拷贝到目标字符串dst

如果src的前n个字符长度中没有\0结束符,则dst中不会包含\0结束符,需要手动补上\0结束符

如果src的前n个字符长度中有\0结束符,则复制到\0的位置终止

dst必须有足够的空间

返回:返回dst

 

#define STRNCMP(dst,src,n)            strncmp(dst,src,n)

要点:比较字符串dstsrc的前n个字符

返回:返回-1,0,1分别表示dst小于、等于或大于src

 

#define SNPRINTF            snprintf

SNPRINTF(char *str,size_t size, const char *format, ...);

要点:格式化输出字符串,最多不超过size_t个字符,包含\0结束符在内

返回:返回假设str空间足够大的时候,应该被写入的所有字符串参数的长度

 

SWORD32 XOS_snprintf(CHAR    *pcBuffer,

                     WORD32  dwMaxCount,

                     const char  *pcFmt,

...)

要点:pcBuffer:待写入缓冲区

dwMaxCount:缓冲区大小,包括\0结束符

待写入字符串超出缓冲区大小时,返回-1

返回:正常返回时,返回实际写入的字节数

 

SWORD32 XOS_strncpy(CHAR *pcBuffer,

const CHAR *pcSource,

WORD32 dwMaxCount)

要点:pcBuffer:待写入缓冲区

pcSource:源数据指针

dwMaxCount:最大写入长度,包括\0结束符

返回:返回实际输出的字节数

 

SWORD32 XOS_strncat(CHAR *pcBuffer,

const CHAR *pcSource,

WORD32 dwMaxCount )

要点:pcBuffer:待写入缓冲区

pcSource:元数据指针

dwMaxCount:最大连接长度,包括\0结束符

返回:返回实际连接的长度

 

0 0
原创粉丝点击