各种输出函数的比较(printf/fprintf/sprintf/snprintf/vprintf/vfprintf/vsprintf/vsnprintf)
對于程序猿來說,printf函數可以說是最熟悉的一個工具了。利用它可以將各類調試信息輸出到指定的設備(比如串口)中,實現對程序運行狀態的掌控和分析。不過,在實際的應用中,相信大家除了printf函數之外,應該還見過幾個與其類似的函數,包括fprintf、sprintf、snprintf、vprintf、vfprintf、vsprintf、vsnprintf等等。那么,這些看上去很類似的函數之間,到底有什么區別,各自的作用到底是什么?今天就來總結一下。
首先列出全部的函數申明,以供參考。
#include <stdio.h>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 <stdarg.h>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 <stdio.h> #include <stdafx.h>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 <iostream> #include <cstring> #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 <iostream> #include <cstring> #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 <iostream> #include <cstring> #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; }我運行的時候,程序沒有崩潰,算是萬幸。 但結果亂碼??磥?#xff0c;沒有自動在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)并沒有按要求實現, 暈。
總結
以上是生活随笔為你收集整理的各种输出函数的比较(printf/fprintf/sprintf/snprintf/vprintf/vfprintf/vsprintf/vsnprintf)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 盖茨炮轰马斯克殖民火星雄心:这是在浪费钱
- 下一篇: linux下查找命令which/wher