abner_ma 发表于 2023-2-23 18:40

深入解析单片机复制函数

   在进行程序设计时,免不了在函数之间进行数据复制。常见的方法主要有:
函数原型:
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_tn);   将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. 根据参数来转换并格式化数据
5.2. 将格式化结果复制到str指向的字符串数组
5.3. 直到出现字符串结束符['\0']为止

    //sprintf用法举例

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buffer;
    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. 根据参数来转换并格式化数据
6.2. 将格式化结果复制到str指向的字符串数组
6.3. 直到出现字符串结束符['\0']或达到size指定大小为止(截断)
//snprintf用法
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char buffer;
    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安全,即不会造成缓冲区溢出


页: [1]
查看完整版本: 深入解析单片机复制函数