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输出到字符串内。

例子:

cpp 复制代码
#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的返回值是欲写入的字符串(即源字符串)长度,而不是实际写入的字符串度。

cpp 复制代码
#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 宏初始化。

返回值:如果成功,则返回写入的字符总数,否则返回一个负数。

例子:

cpp 复制代码
#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将格式化后的字符串输出到一个已经分配好的缓冲区中,需要手动指定缓冲区的大小。

返回值:如果成功,则返回写入的字符总数,否则返回一个负数。

cpp 复制代码
#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函数:
方法一

cpp 复制代码
#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函数,功能一致:

cpp 复制代码
#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;
}

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

cpp 复制代码
#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)

相关推荐
2401_858286114 分钟前
101.【C语言】数据结构之二叉树的堆实现(顺序结构) 下
c语言·开发语言·数据结构·算法·
Beau_Will10 分钟前
数据结构-树状数组专题(1)
数据结构·c++·算法
迷迭所归处14 分钟前
动态规划 —— 子数组系列-单词拆分
算法·动态规划
爱吃烤鸡翅的酸菜鱼14 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
寻找码源1 小时前
【头歌实训:利用kmp算法求子串在主串中不重叠出现的次数】
c语言·数据结构·算法·字符串·kmp
Matlab精灵1 小时前
Matlab科研绘图:自定义内置多款配色函数
算法·matlab
诚丞成1 小时前
滑动窗口篇——如行云流水般的高效解法与智能之道(1)
算法
带多刺的玫瑰3 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔3 小时前
《线性代数的本质》
线性代数·算法·决策树
yigan_Eins3 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法