Linux:sprintf、snprintf、vsprintf、asprintf、vasprintf比较

2024-04-11 07:20

本文主要是介绍Linux:sprintf、snprintf、vsprintf、asprintf、vasprintf比较,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这些函数都在stdio.h里,不过不同系统不同库,有些函数不一定提供。

1. sprintf

函数原型:

int sprintf (char *str, const char *format, ...);
extern int sprintf (char *__restrict __s, const char *__restrict __format, ...);

功能是将格式化输出,打印到str所指向的字符串内存里边,参数str是一已分配好的内存,后面跟随格式化输出。使用和printf类似,只是sprintf输出到字符串内。
例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int test_sprintf()
{char *s1 = malloc(16 * sizeof(char));memset(s1, 16, 0);int ret = sprintf(s1, "sprintf %s %d", "test1", 7); // 测试正常用法printf("ret=%d, s1=%s,\n", ret, s1);memset(s1, 16, 0);ret = sprintf(s1, "0123456789 %s %d", "123456", 7); //测试超过长度printf("ret=%d, s1_strlen=%d, s1=%s,\n", ret, strlen(s1), s1);free(s1);char s2[16] = {0};ret = sprintf(s2, "sprintf %s, %d", "t2", 8); // 测试正常用法printf("ret=%d, s2=%s\n", ret, s2);ret = sprintf(s2, "0123456789 %s %d", "123456", 7); // 测试超过长度printf("ret=%d, s2=%s\n", ret, s2);char *s3 = NULL;ret = sprintf(s3, "sprintf %s, %d", "test3", 8); // 测试空指针printf("ret=%d, s2=%s", ret, s3);
}int main()
{test_sprintf();return 0;
}

输出结果:

ret=15, s1=sprintf test1 7, # 返回值为实际字符串的长度,不含'\0'
# 当格式化字符串长度超过已申请的内存大小,依然会执行,不报错,发生内存踩踏,
# 这将导致概率性运行内存错误(访问到不可访问的内存时会奔溃)
# 返回值依然是实际打印的格式化字符串的长度
ret=19, s1_strlen=19, s1=0123456789 123456 7,
ret=13, s2=sprintf t2, 8
ret=19, s2=0123456789 123456 7 # 也一样的
Segmentation fault # 目标指针是空指针时,编译不报错,运行奔溃

可见sprintf不会对内存长度进行检查,导致内存越界访问,踩踏后程序可能依然正常运行,很不容易发现问题,建议使用安全函数。

2. snprintf

函数原型

int snprintf (char *str, size_t maxlen, const char *format, ...);
/* Maximum chars of output to write in MAXLEN.  */
extern int snprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, ...);

用法也与printf类似,也是把格式化字符串输出给一块str所指向的char类型的内存数组(空间),maxlen是要写入的最大数目,超过n会被截断,后面则与printf一样的格式化字符串。
返回值: 成功则返回参数str 字符串长度,失败则返回-1,错误原因存于errno 中。需要注意的是snprintf的返回值是欲写入的字符串(即源字符串)长度,而不是实际写入的字符串度。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int test_snprintf()
{char *s1 = malloc(16 * sizeof(char));memset(s1, 16, 0);int ret = snprintf(s1, 16, "sprintf %s %d", "test1", 7); // 测试正常用法printf("ret=%d, s1=%s,\n", ret, s1);memset(s1, 16, 0);ret = snprintf(s1, 16, "0123456789 %s %d", "123456", 7); //测试超过长度printf("ret=%d, s1_strlen=%d, s1=%s,\n", ret, strlen(s1), s1);free(s1);char s2[16] = {0};ret = snprintf(s2, sizeof(s2), "sprintf %s, %d", "t2", 8); // 测试正常用法printf("ret=%d, s2=%s\n", ret, s2);ret = snprintf(s2, sizeof(s2), "0123456789 %s %d", "123456", 7); // 测试超过长度printf("ret=%d, s2=%s\n", ret, s2);char *s3 = NULL;ret = snprintf(s3, 0, "sprintf %s, %d", "test3", 8); // 测试空指针printf("ret=%d, s2=%s\n", ret, s3);
}int main()
{test_snprintf();return 0;
}

输出结果:

ret=15, s1=sprintf test1 7,
# 返回值是字符串实际长度,但未发生内存越界,字符串截断成内存大小,且留有'\0'
ret=19, s1_strlen=15, s1=0123456789 1234,
ret=13, s2=sprintf t2, 8
ret=19, s2=0123456789 1234 # 栈上内存也一样
ret=16, s2=(null) # 当传入空指针时,可获得存放格式化输出需要多少字节内存

snprintf通过限定maxlen(通常是申请的内存大小),保证内存不被越界访问。传入空指针的情况,可用于获取存放格式化字符串需要多少内存,用于malloc。

3. vprintf

        使用参数列表发送格式化输出到标准输出 stdout,函数定义:

int vprintf(const char *format, va_list arg);
/* Write formatted output to stdout from argument list ARG.
   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int vprintf (const char *__restrict __format, _G_va_list __arg);

        参数arg: 一个表示可变参数列表的对象。这应被 中定义的 va_start 宏初始化。
返回值:如果成功,则返回写入的字符总数,否则返回一个负数。
例子:

#include <stdio.h>
#include <stdarg.h>void WriteFrmtd(char *format, ...)
{va_list args;va_start(args, format);vprintf(format, args); // 传入参数列表va_end(args);
}int main ()
{WriteFrmtd("%d variable argument\n", 1);WriteFrmtd("%d variable %s\n", 2, "arguments");return(0);
}
// 输出结果
/*
1 variable argument
2 variable arguments
*/

与printf类似,能够用于构建出自己的不定参数的函数。

4. vsprintf

        有了vprintf理解,也就很好理解vsprintf了,vsprintf只是输出目标换成一char内存区域(或叫字符串)。

int vsprintf(char *str, const char *format, va_list arg);
 
/* Write formatted output to S from argument list ARG.  */
extern int vsprintf (char *__restrict __s, const char *__restrict __format,  _G_va_list __arg) __THROWNL;

        vsprintf将格式化后的字符串输出到一个已经分配好的缓冲区中,需要手动指定缓冲区的大小。
返回值:如果成功,则返回写入的字符总数,否则返回一个负数。

#include <stdio.h>
#include <stdarg.h>char buffer[80];
int vspfunc(char *format, ...)
{va_list aptr;int ret;va_start(aptr, format);ret = vsprintf(buffer, format, aptr); // 传入参数列表va_end(aptr);return(ret);
}int main()
{int i = 5;float f = 27.0;char str[50] = "runoob.com";vspfunc("%d %f %s", i, f, str);printf("%s\n", buffer);return(0);
}
// 输出结果:5 27.000000 runoob.com

可见写入str时也有安全风险。

5. vsnprintf

        与第二个snprintf函数类似,多了个限定内存maxlen,更加安全,其他用法一致。

int vsnprintf (char *str, size_t maxlen, const char *format, va_list arg);// 简化声明
int vsnprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, _G_va_list __arg);

6. asprintf

        vasprintf会自动分配足够大的缓冲区来存储格式化后的字符串,并将指向该缓冲区的指针作为返回值。ptr传入已空指针即可。

int asprintf (char **ptr, const char *fmt, ...)
int asprintf (char **__restrict __ptr, const char *__restrict __fmt, ...)

asprintf在有些系统库里没有提供,可用以下方法替换asprintf函数:
方法一

#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int test_asprintf(char **buf, const char *fmt, ...)
{va_list ap;va_start(ap, fmt);int ret = vasprintf(buf, fmt, ap);if (ret < 0) {*buf = NULL;}va_end(ap);return ret;
}

        但很多系统同样也没提供vasprintf函数,可使用方法二替换asprintf函数,功能一致:

#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int test_asprintf(char **buf, const char *fmt, ...)
{*buf = NULL;va_list ap;va_start(ap, fmt);int count = vsnprintf(NULL,  0, fmt, ap);va_end(ap);if (count < 0) {return count;}char *buffer = malloc(count + 1);if (buffer == NULL) {return -1;}buffer[count] = 0;va_start(ap, fmt);count = vsnprintf(buffer,  count + 1, fmt, ap);if (count < 0) {free(buffer);} else {*buf = buffer;}va_end(ap);return count;
}

使用参数列表过程中,这里发现过一个问题

#include <stdio.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int dsl_append_sprintf(char **buf, const char *fmt, ...)
{//错误写法va_list ap;va_start(ap, fmt);*buf = NULL;int count = vsnprintf(NULL,  0, fmt, ap);if (count >= 0) {char *buffer = malloc(count + 1);if (buffer != NULL) {buffer[count] = 0;count = vsnprintf(buffer,  count + 1, fmt, ap);if (count < 0) {free(buffer);} else {*buf = buffer;}}}va_end(ap);return count;
}
int main()
{char *buffer;dsl_append_sprintf(&buffer, "str= %s, data= %d\n", "test", 25);printf("%s", buffer);return 0;
}
// 输出 str= , data= 2564972

        这里的写法是只用了一次va_start,被vsnprintf两次使用,导致第二次使用时,拿到的是错误的内存值。

7.vasprintf

        这个和asprintf类似,只是参数是va_list

/* Write formatted output to a string dynamically allocated with `malloc'.
   Store the address of the string in *PTR.  */
extern int vasprintf (char **__restrict __ptr, const char *__restrict __f,  _G_va_list __arg)

8. fprintf

/* Write formatted output to STREAM.
   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int fprintf (FILE *__restrict __stream,  const char *__restrict __format, ...);

9. vfprintf

/* Write formatted output to S from argument list ARG.
 
   This function is a possible cancellation point and therefore not
   marked with __THROW.  */
extern int vfprintf (FILE *__restrict __s, const char *__restrict __format,  _G_va_list __arg);
 

10. vdprintf

/* Write formatted output to a file descriptor.  */
extern int vdprintf (int __fd, const char *__restrict __fmt,  _G_va_list __arg)

这篇关于Linux:sprintf、snprintf、vsprintf、asprintf、vasprintf比较的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/893430

相关文章

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。