发新帖本帖赏金 20.00元(功能说明)我要提问
返回列表
打印
[C语言]

深入解析单片机复制函数

[复制链接]
511|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
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_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安全,即不会造成缓冲区溢出


使用特权

评论回复

打赏榜单

21ic小管家 打赏了 20.00 元 2023-03-13
理由:签约作者奖励

相关帖子

发新帖 本帖赏金 20.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:项目经理
简介:资深嵌入式开发工程师

83

主题

155

帖子

3

粉丝