各种输出函数的比较(printf/fprintf/sprintf/snprintf/vprintf/vfprintf/vsprintf/vsnprintf)

对于程序猿来说,printf函数可以说是最熟悉的一个工具了。利用它可以将各类调试信息输出到指定的设备(比如串口)中,实现对程序运行状态的掌控和分析。不过,在实际的应用中,相信大家除了printf函数之外,应该还见过几个与其类似的函数,包括fprintf、sprintf、snprintf、vprintf、vfprintf、vsprintf、vsnprintf等等。那么,这些看上去很类似的函数之间,到底有什么区别,各自的作用到底是什么?今天就来总结一下。

首先列出全部的函数申明,以供参考。

#include 

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

#include 

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

怎么样,是不是看的有点晕?没关系,我们可以先用一个表格来大致区分一下上述这些函数的异同点,就不会那么晕了。

参数类型 输出到设备 输出到文件 输出到字符串
可变参数 printf fprintf sprintf、snprintf
固定参数 vprintf vfprintf vsprintf、vsnprintf

OK,下面我们就来逐个进行详细的介绍和比对。

一、printf和vprintf

多数情况下使用printf() 。只有当你需要自己写一个printf()那样的专有函数的时候才需要vprintf()。比如你写一个自己专门的错误输出函数:

int error(char *fmt, ...)
{
    int result;
    va_list args;

    va_start(args, fmt);
    // 一些内容
    va_end(args);

    return result;
}

应当注意到,你不能转发参数给printf,因为printf是变长参数的,而不是vprintf的单独一个va_list。然而vprintf() 函数, 只取一个合并的va_list 参数, 所以完整的版本是:

int error(char *fmt, ...)
{
    int result;
    va_list args;

    va_start(args, fmt);

    fputs("Error: ", stderr);

    result = vfprintf(stderr, fmt, args);

    va_end(args);

    return result;
}

二、fprintf和vfprintf

两个函数从声明看,第三个参数有区别,这样就形成了两个函数不同的作用。比如,你要写一个日志函数:

void log(FILE *file, const char* format, ... )
{
    va_list args;

    va_start (args, format);

    fprintf(file, "%s: ", getTimestamp());

    vfprintf (file, format, args);      //在这个地方用vfprintf函数就很合适,因为第三个参数可以直接得到

    va_end (args);
}

vfprintf适合参数可变列表传递。

三、sprintf和vsprintf

先看一个例子:

#include 
#include 

int _tmain(int argc, _TCHAR* argv[])
{
    char *p1="China";
    char  a[20];

    sprintf(a,"%s",p1);
    printf("%s\n",a);

    memset(a,0,sizeof(a));
    snprintf(a,3,"%s",p1);
    printf("%s\n",a);
    printf("%d\n",strlen(a));

    return 0;
}

结果输出:

China
Chi
3

过程分析:
sprintf(a,”%s”,p1) 把p1字符串拷贝到数组a中(‘\0’也拷贝过去了)。
snprintf(a,3,”%s”,p1) 拷贝P1中前3个字符到数组a中,并在末尾自动添加’\0’。

sprintf属于I/O库函数,snprintf函数并不是标准c/c++中规定的函数,但是在许多编译器中,厂商提供了其实现的版本。在gcc中,该函数名称就snprintf,而在VC中称为_snprintf。 如果你在VC中使用snprintf(),会提示此函数未声明,改成_snprintf()即可。

注意点:
1 sprintf是一个不安全函数,src串的长度应该小于dest缓冲区的大小,(如果src串的长度大于或等于dest缓冲区的大小,将会出现内存溢出。)
2 snprintf中源串长度应该小于目标dest缓冲区的大小,且size等于目标dest缓冲区的大小。(如果源串长度大于或等于目标dest缓冲区的大小,且size等于目标dest缓冲区的大小,则只会拷贝目标dest缓冲区的大小减1个字符,后加’\0’;该情况下,如果size大于目标dest缓冲区的大小则溢出。)
3 snprintf ()函数返回值问题, 如果输出因为size的限制而被截断,返回值将是“如果有足够空间存储,所应能输出的字符数(不包括字符串结尾的’\0’)”,这个值和size相等或者比size大!也就是说,如果可以写入的字符串是”0123456789ABCDEF”共16位,但是size限制了是10,这样 snprintf() 的返回值将会是16 而不是10!

四、snprintf和vsnprintf

同样来看一个例子:

#include   
#include   
#define snprintf _snprintf  
using namespace std;  

int main()  
{  
    char str[10] = {0};  
    char *data = "abcdefg";  
    sprintf(str, "debug : %s", data);  

    cout << str << endl;  
    return 0;  
}   

该程序可以编译过,但是在运行期间会崩溃,原因相信大家都能看的出来。那么,应该如何处理呢?

#include   
#include   
#define snprintf _snprintf  
using namespace std;  

int main()  
{  
    char str[10] = {0};  
    char *data = "abcdefg";  
    snprintf(str, sizeof(str) - 1, "debug : %s", data);  

    cout << str << endl;  
    return 0;  
}  

这样就安全了,和strncpy非常类似。

另外,需要特别注意的是: Windows和Linux中的snprintf函数有区别, 在linux代码中,经常见到snprintf(str, sizeof(str), “…”)这样的用法, 为什么这里不是sizof(str) - 1呢?

我们看看Windows下这么用会怎样:

#include   
#include   
#define snprintf _snprintf  
using namespace std;  

int main()  
{  
    char str[10] = {0};  
    char *data = "abcdefgddddddddddddddddddddd";  
    snprintf(str, sizeof(str), "debug : %s", data);  

    cout << str << endl;  
    return 0;  
}  

我运行的时候,程序没有崩溃,算是万幸。 但结果乱码。看来,没有自动在str最后加’\0’, 在linux中, 就安全了, 会自动补哈, 所以永远不会越界。

总结一下:
1. Linux中, 对于snprintf, 用sizeof(str), 最后会自动加’\0’, 比strncpy更安全省事。
2. Windows中, 就把snprintf和strncpy理解为类似的, 要用sizeof(str) - 1, 需要注意最后的’\0’, 当然啦,你可以在每次用strncpy之前,利用memset将串清零, 这样比较好。VC++6.0中的_snprintf(snprintf)并没有按要求实现, 晕。

你可能感兴趣的:(其他东东)