這兩天在項目原有版本的基礎上增加了一段新的協議實現代碼,因此需要和平臺進行聯調。考慮到更好地進行調試,我在代碼中添加了一段類似日誌記錄的代碼,已獲取通訊的報文內容和當時的環境參數內容,就是創建一個文件,使用標準IO的fopen、fprintf進行輸出記錄。但是在調試中,剛開始我就傻眼了,文件創建成功了,但是實時查看竟然沒有任何數據記錄。經過半天的擔驚受怕和反覆排查,發現是被標準IO的緩衝機制擺了一道,慚愧呀。。。
下面給出一個示例程序,模擬我的項目程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
FILE* fp=NULL;
const char *filename_1="test_fprintf.log";
const char *filename_2="test_write.log";
int fd;
fp = fopen(filename_1, "wb");
if(fp == NULL)
{
printf("open %s failed, %s\n", filename_1, strerror(errno));
return -1;
}
//setbuf(fp, NULL);
//setvbuf(fp, NULL, _IONBF, 0);
fd = open(filename_2, O_WRONLY|O_CREAT|O_EXCL, 0666);
if(fd < 0)
{
printf("open %s failed, %s\n", filename_2, strerror(errno));
return -1;
}
while(1)
{
fprintf(fp, "test fprintf.\n");
fprintf(fp, "-------test fprintf.\n");
fprintf(fp, "=======test fprintf.\n");
//fflush(fp);
write(fd, "test open.\n", sizeof("test open.\n"));
write(fd, "--------test open.\n", sizeof("--------test open.\n"));
write(fd, "--------test open.\n", sizeof("--------test open.\n"));
sleep(1);
}
return 0;
}
後臺運行上面的示例程序,然後實時查看兩個日誌文件,會發現testfrpintf.log文件一開始一直都是空的,而testwrite.log則是不斷有數據寫入,如下狀態:
我當時就是奇怪爲什麼文件會是空的。可以看出標準IO會緩衝4096Bytes的數據,當達到這麼多數據時纔會進行實際的磁盤寫入,而系統調用write則是直接寫入,不進行緩衝。
標準IO庫提供緩衝的目的是儘可能減少使用read和write調用的次數,降低執行IO的時間,它提供三種類型的緩衝:
- 全緩衝。在填滿標準IO緩衝區後才進行實際IO操作,對於磁盤文件通常就是全緩衝,上面的示例就是採用緩衝。
- 行緩衝。在輸入和輸出中遇到換行符時進行實際的IO操作,當涉及到一個終端時,通常使用行緩衝。使用最頻繁的printf函數就是採用行緩衝,所以感覺不出緩衝的存在。
- 不帶緩衝。標準IO庫不對字符進行緩衝存儲。標準出錯流stderr通常是不帶緩衝的。
ISO C要求下列緩衝特徵:
- 當且僅當標準輸入和標準輸出並不涉及交互式設備時,它們纔是全緩衝的。
- 標準出錯決不會是全緩衝。
很多系統默認使用下列類型的緩衝:
- 標準出錯是不帶緩衝的。
- 如若是涉及終端設備的其它流,則他們是行緩衝的;否則是全緩衝的。
當然,對於標準IO流,我們也可以更改緩衝類型,或者是直接刷新。ISO C中提供下面兩個函數以更改緩衝類型:
1 |
void setbuf ( FILE *fp, char *buf); //buf爲NULL,表示關閉緩衝 |
2 |
int setvbuf ( FILE *fp, char *buf, int mode, size_t size); //成功返回0,出錯返回非0值 |
setvbuf函數中的mode參數可以爲:_IOBUF 全緩衝, _IOLBF 行緩衝, _IONBF 不帶緩衝,如果buf爲NULL, 則標準IO庫將自動地爲該流分配適當長度(常量BUFSIZ)的緩衝區。一般而言,應由系統選擇緩衝區的長度,並自動分配緩衝區,這樣關閉流時,標準IO庫將自動釋放緩衝區。
強制沖洗一個流,使用函數:
1 |
int fflush ( FILE *fp); //成功返回0, 出錯返回EOF |
項目中我是使用這個函數解決鬱悶的。
1 |
fflush (NULL); //沖洗所有輸出流 |