7

C经典面试题之深入解析sprintf、strcpy和memcpy的使用与区别

 2 years ago
source link: https://blog.csdn.net/Forever_wj/article/details/120380001
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、sprintf

① sprintf 定义

  • sprintf 指的是字符串格式化命令,是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串,直到出现字符串结束符 ‘\0’ 为止。sprintf 函数的声明如下:
int sprintf(char *string, char *format [,argument,...]);
  • 参数列表:
    • string:这是指向一个字符数组的指针,该数组存储了 C 字符串;
    • format:这是字符串,包含了要被写入到字符串 str 的文本,它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化,它的标签属性是 %[flags][width][.precision][length]specifier;
    • [argument]…:根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签,参数的个数应与 % 标签的个数相同。
  • 返回值为字符串长度(strlen):
    • 如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符;如果失败,则返回一个负数;
    • sprintf 返回以 format 为格式,argument 为内容组成的结果被写入 string 的字节数,结束字符‘\0’不计入内,即如果“Hello”被写入空间足够大的 string 后,函数 sprintf 返回5。
  • sprintf 是个变参函数,使用 sprintf 对于写入 buffer 的字符数是没有限制的,这就存在了 buffer 溢出的可能性。解决这个问题,可以考虑使用 snprintf 函数,该函数可对写入字符数做出限制。
  • 相关函数:
int sprintf_s(char *buffer,size_t sizeOfBuffer,const char *format, [argument] ... );
int _sprintf_s_l(char *buffer,size_t sizeOfBuffer,const char *format,locale_t locale ,[argument] ... );
int swprintf_s(wchar_t *buffer,size_t sizeOfBuffer,const wchar_t *format ,[argument]...);
int _swprintf_s_l(wchar_t *buffer,size_t sizeOfBuffer,const wchar_t *format,locale_t locale ,[argument]…);
template <size_t size>
int sprintf_s(char (&buffer)[size],const char *format, [argument] ... );      // 仅存在于C++
template <size_t size>
int swprintf_s(wchar_t (&buffer)[size],const wchar_t *format ,[argument]...); // 仅存在于C++

② 使用示例

  • 格式化字符串:
char buf[1024] = { 0 };
sprintf(buf, "Hello %s", "world");
printf("buf:%s\n", buf);

memset(buf, 0, 1024);
sprintf(buf, "今年是%d年", 2021);
printf("buf:%s\n", buf);

// 执行结果
buf:Hello World
buf:今年是2021年
  • 拼接字符串:
char buf[1024] = { 0 };
memset(buf, 0, 1024);
char str1[] = "Hello";
char str2[] = "world!";
int len = sprintf(buf,"%s %s", str1, str2);
printf("buf:%s len:%d\n", buf, len);

// 执行结果
buf:Hello world! len:12
  • 数字转字符串:
char buf[1024] = { 0 };
memset(buf, 0, 1024);
int num = 100;
sprintf(buf, "%d", num);
printf("buf:%s\n", buf);

// 设置宽度 右对齐
memset(buf, 0, 1024);
sprintf(buf, "%8d", num);
printf("buf:%s\n", buf);
// 设置宽度 左对齐
memset(buf, 0, 1024);
sprintf(buf, "%-8d", num);
printf("buf:%s\n", buf);

// 执行结果
buf:100
buf:     100
buf:100
  • 转成 16 进制字符串:
char buf[1024] = { 0 };
memset(buf, 0, 1024);
sprintf(buf, "0x%x", num);
printf("buf:%s\n", buf);

// 执行结果
buf:0x64
  • 转成 8 进制字符串:
char buf[1024] = { 0 };
memset(buf, 0, 1024);
sprintf(buf, "0%o", num);
printf("buf:%s\n", buf);

// 执行结果
buf:0144

③ 常见问题

  • 缓冲区溢出:第一个参数的地址空间长度太短,因此需要给足够大的地址空间。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。
  • 变参对应出问题:通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,尤其是对应”*”的那些参数,都需要提供。不要把一个整数对应一个”%s”,编译器会编译出错。
  • sprintf_s 和 snprintf:sprintf_s() 是 sprintf() 的安全版本,通过指定缓冲区长度来避免 sprintf() 存在的溢出风险。在使用 VS2008 时如果使用了 sprintf 函数,那么编译器会发出警告:使用 sprintf 存在风险,建议使用 sprintf_s。
  • strftime:sprintf 还有个 strftime,专门用于格式化时间字符串的,用法与之很像,也是一大堆格式控制符,只是还要调用者指定缓冲区的最大长度。

二、strcpy

① strcpy 定义

  • strcpy,即 string copy(字符串复制)的缩写。strcpy 是 C++ 语言的一个标准函数,strcpy 把含有 ‘\0’ 结束符的字符串复制到另一个地址空间 dest,返回值的类型为 char*。
  • strcpy 函数把从 src 地址开始且含有 NULL 结束符的字符串复制到以 dest 开始的地址空间,声明如下:
char *strcpy(char *dest, const char *src)
    • dest:指向用于存储复制内容的目标数组;
    • src:要复制的字符串。
  • 返回值:该函数返回一个指向最终的目标字符串 dest 的指针。
  • 说明:src 和 dest 所指内存区域不可以重叠且 dest 必须有足够的空间来容纳 src 的字符串。

② 使用示例

char src[40];
char dest[100];
memset(dest, '\0', sizeof(dest));
strcpy(src, "hello world!");
strcpy(dest, src);
printf("最终的目标字符串:%s\n", dest);

// 执行结果
最终的目标字符串:hello world!

三、memcpy

① memcpy 定义

  • memcpy 指的是 C 和 C++ 使用的内存拷贝函数,功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源 source 中拷贝 n 个字节到目标 destin 中。
  • memcpy 函数的声明为 :
void *memcpy(void *destin, void *source, unsigned n)
    • destin:指向用于存储复制内容的目标数组,类型强制转换为 void* 指针;
    • source:指向要复制的数据源,类型强制转换为 void* 指针;
    • n:要被复制的字节数。
  • 返回值:该函数返回一个指向目标存储区 destin 的指针。
    • source 和 destin 所指内存区域不能重叠,函数返回指向 destin 的指针;
    • 与 strcpy 相比,memcpy 并不是遇到 ‘\0‘ 就结束,而是一定会拷贝完 n 个字节。

② 使用示例

  • 复制后覆盖原有部分数据:
char src[] = "******";
char dest[] = "ABCDEFGHIJK";
printf("destinationbefore memcpy: %s\n", dest);
memcpy(dest, src, strlen(src));
printf("destinationafter memcpy: %s\n", dest);

// 执行结果
destinationbefore memcpy: ABCDEFGHIJK
destinationafter memcpy: ******GHIJK
  • 从 0 开始,将 s 中第 14 个字符开始的 4 个连续字符复制到 d 中:
char *s = "Golden Global View";
char d[20];
// 从第14个字符(V)开始复制,连续复制4个字符(View)
memcpy(d,s+14,4);
d[4] = '\0';
printf("%s",d);

// 执行结果
View
  • 将 s 中的字符串复制到字符数组 d 中:
char *s = "Golden Global View";
char d[20];
memcpy(d,s,strlen(s));
// 因为从d[0]开始复制,总长度为strlen(s),d[strlen(s)]置为结束符
d[strlen(s)] = '\0';
printf("%s",d);

// 执行结果
Golden Global View
  • source 和 destin 所指的内存区域可能重叠,但是如果 source 和 destin 所指的内存区域重叠,那么这个函数并不能够确保 source 所在重叠区域在拷贝之前不被覆盖。而使用 memmove 可以用来处理重叠区域,函数返回指向 destin 的指针。
  • 如果目标数组 destin 本身已有数据,执行 memcpy() 后,将覆盖原有数据(最多覆盖 n)。如果要追加数据,则每次执行 memcpy 后,要将目标数组地址增加到你要追加数据的地址。
  • source 和 destin 都不一定是数组,任意的可读写的空间均可。

四、三者的区别

① 实现功能和操作对象不同

  • strcpy 函数操作的对象是字符串 ,完成从源字符串到目的字符串的拷贝功能;
  • snprintf 函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串,也可以是任意基本类型的数据;这个函数主要用来实现 (字符串或基本数据类型)向字符串的转换 功能,如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能;
  • memcpy 函数顾名思义就是内存拷贝 ,实现将一个 内存块 的内容复制到另一个内存块这一功能,内存块由其首地址以及长度确定,程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。

② 实现的效率和使用的方便程度不同

  • 对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:
    • strcpy 无疑是最合适的选择:效率高且调用方便;
    • snprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高;
    • memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。
    • 其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。
  • 对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。
  • 另外,strcpy 和 memcpy 功能上也有些差别:比如:const char *str1=“abc/0def”; char str2[7];
    • 首先用 strcpy 实现: strcpy(str2,str1) 得到结果:str2="abc"; 也就是说,strcpy是以 ‘/0’ 为结束标志的;
    • 再用 memcpy 实现: memset(str2,7); memcpy(str2,str1,7); 得到结果:str2=“abc/0def”; 也就是说,memcpy是对内存区域的复制。当然,不仅能够复制字符串数组,而且能够复制整型数组等其他数组。
  • strcpy 是一个字符串拷贝的函数,它的函数原型为 strcpy(char dst, const char src) ; 将 src 开始的一段字符串拷贝到 dst 开始的内存中去,结束的标志符号为 ‘\0’,由于拷贝的长度不是由我们自己控制的,所以这个字符串拷贝很容易出错;
  • 具备字符串拷贝功能的函数有 memcpy,这是一个内存拷贝函数,它的函数原型为 memcpy (char dst, const char src, unsigned int len) ; 将长度为 len 的一段内存,从 src 拷贝到 dst 中去,这个函数的长度可控,但是会有内存读写错误(比如 len的长度大于要拷贝的空间或目的空间);
  • sprintf 是格式化函数,将一段数据通过特定的格式,格式化到一个字符串缓冲区中去;sprintf 格式化的函数的长度不可控,有可能格式化后的字符串会超出缓冲区的大小,造成溢出。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK