[C语言]深入解析单片机复制函数

    在进行程序设计时,免不了在函数之间进行数据复制。常见的方法主要有:
函数原型:
1.char *strcpy(char* dest, const char *src)   
  把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
  1.1.strcpy函数在拷贝时也会把源字符串中的'\0'拷贝到目标字符串中作为拷贝结束的标志。
  1.2.目标空间必须足够大存放的下源字符串,放不下源字符串中的内容,程序会崩溃。
   原型:
 



//strcpy(dest,src)把从src地址开始且含有null结束符的字符串复制到以dest开始的地址空间

char *strcpy(char *strDest, const char *strSrc)

{

  char *res=strDest;

  assert((strDest!=NULL)&&(strSrc!=NULL));

  while((*strDest=*strSrc)!='\0')

  {

    strDest++;

    strSrc++;

  }

  return res;

}

2.void *memcpy(void *dest, const void *src, size_t n);
  从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
  window下函数原型:
void* __cdecl memcpy(void* dst,const void* src,size_t count)

{

    void*ret=dst;

  #if defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)

  {

       extern void RtlMoveMemory(void *,const void *,size_t count);

       RtlMoveMemory(dst,src,count);

  }

  #else /*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/

  /*

  *copy from lower addresses to higher addresses

  */

  while(count--){

      *(char *)dst = *(char *)src;

      dst = (char *)dst+1;

      src = (char *)src+1;

  }

  #endif  /*defined(_M_MRX000)||defined(_M_ALPHA)||defined(_M_PPC)*/

  return (ret);

}

  linux系统下:

void *memcpy(void *to, const void *from, size_t n)

{   //记录拷贝目的位置,为了返回拷贝内容的首地址

    void *xto = to;

    size_t temp, temp1;

     //判断拷贝的字节数

    if (!n)

        return xto;   //

    if ((long)to & 1) {

        char *cto = to;

        const char *cfrom = from;

        *cto++ = *cfrom++;

        to = cto;

        from = cfrom;

        n--;

    }

    if (n > 2 && (long)to & 2) {

        short *sto = to;

        const short *sfrom = from;

        *sto++ = *sfrom++;

        to = sto;

        from = sfrom;

        n -= 2;

    }

    temp = n >> 2;

    if (temp) {

        long *lto = to;

        const long *lfrom = from;

  #if defined(CONFIG_M68000) || defined(CONFIG_COLDFIRE)

        for (; temp; temp--)

            *lto++ = *lfrom++;

  #else

        asm volatile (

            "    movel %2,%3\n"

            "    andw  #7,%3\n"

            "    lsrl  #3,%2\n"

            "    negw  %3\n"

            "    jmp   %%pc@(1f,%3:w:2)\n"

            "4:    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "    movel %0@+,%1@+\n"

            "1:    dbra  %2,4b\n"

            "    clrw  %2\n"

            "    subql #1,%2\n"

            "    jpl   4b"

            : "=a" (lfrom), "=a" (lto), "=d" (temp), "=&d" (temp1)

            : "0" (lfrom), "1" (lto), "2" (temp));

  #endif

        to = lto;

        from = lfrom;

      }

     if (n & 2) {

        short *sto = to;

        const short *sfrom = from;

        *sto++ = *sfrom++;

        to = sto;

        from = sfrom;

      }

      if (n & 1) {

          char *cto = to;

          const char *cfrom = from;

          *cto = *cfrom;

      }

    return xto;

}

   strcpy和memcpy主要有三方面的区别。

其一:复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
其二:复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,如果空间不够,就会引起内存溢出。memcpy则是根据其第3个参数决定复制的长度。
其三:用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。

3.void *memmove( void* dest, const void* src, size_t count );   由src所指内存区域复制count个字节到dest所指内存区域。
4.void *memset(void *s, int ch,  size_t  n);   将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s。
5.int sprintf(char *str, const char *format, ...);
功能:将格式化的数据写入某个字符串缓冲区
入参:format,输出字符串的格式化列表,比如%d、%s、%c等
入参:format对应的不定参数列表,与printf类似
出参:buffer,指向一段存储空间,用于存储格式化之后的字符串
返回值:返回写入buffer的字符数,出错则返回-1

5.1. 根据参数[format字符串]来转换并格式化数据
5.2. 将格式化结果复制到str指向的字符串数组
5.3. 直到出现字符串结束符['\0']为止

    //sprintf用法举例



#include <stdio.h>

#include <stdlib.h>

int main()

{

    char buffer[10];

    const int a = 12345;

    const char *b = "102938465839202";

    

    sprintf(buffer, "%d", a);  //将变量a按int类型打印成字符串,输出到buffer中

    sprintf(buffer, "%d+%s", a, b);  //将变量a和字符串b连接成一个字符串输出到buffer中

    

    return 0;

}

  程序分析:
  如果输出到buffer的内容长度不超过10个字节,那么此时sprintf的操作是没有风险的;
  如果超过了10个字节,那么就会导致buffer存储空间溢出,从存储位置上分析,buffer空间是一个栈空间,在它自己10个字节以外的空间是其他栈变量的存储空间,一旦sprintf将10字节外的其他空间也操作了,这就有可能破坏了其他栈变量的内容,程序崩溃发生。


6.int snprintf(char *str, size_t size, const char *format, ...);
6.1. 根据参数[format字符串]来转换并格式化数据
6.2. 将格式化结果复制到str指向的字符串数组
6.3. 直到出现字符串结束符['\0']或达到size指定大小为止(截断)

//snprintf用法

#include <stdio.h>

#include <stdlib.h>

int main()

{

    char buffer[10];

    const int a = 12345;

    const char *b = "102938465839202";

    

    snprintf(buffer, sizeof(buffer), "%d", a);  //将变量a按int类型打印成字符串,输出到buffer中

    snprintf(buffer, sizeof(buffer), "%d+%s", a, b);  //将变量a和字符串b连接成一个字符串输出到buffer中

    

    return 0;

}

分析:
1. windows上无snprintf,但是有_snprintf可以达到同样的功能,但是细节之处略有不同
  windows下没有snprintf相关的库函数。
  解决方案:声明snprintf在windows下对应的库函数。
 

#ifdef _WIN32

        //#define snprintf _snprintf

#endif

#ifdef _MSC_VER

        #define snprintf _snprintf

#endif


2. 未溢出时,sprintf和snprintf都会在字符串末尾加上'\0';
3. 超出缓冲区大小时,_snprintf不会造成溢出,只是不会在缓冲区中添加字符结束符
4. sprintf始终会在字符串末尾添加结束符,但是存在缓冲区溢出的情况
5. _snprintf比sprintf安全,即不会造成缓冲区溢出
---------------------
作者:abner_ma
链接:https://bbs.21ic.com/icview-3285996-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值