使用write()函數和printf()函數輸出一個字符串到終端

引子:

printf("printf123");

write(stdou,"write123",3);

使用write()函數和printf()函數輸出一個字符串到終端的時候,會發現,如果不在printf()中包含換行符 \n就會出現,明明是printf()函數寫在前面,而write()中要輸出的結果先輸出到終端

分析:

1. Linux系統的三種緩存機制:

全緩存:當遇到緩衝區滿/文件關閉/fflush()強制刷新緩存區/程序結束這四種情況以後,才刷新緩存區,將緩存的內容送到內核,如使用fopen 打開的文件,而open打開的文件是沒有緩存的

行緩存:與全緩存相比,行緩存顧名思義,再遇到 \n也會刷新緩存區,比上面的全緩存多了一種刷新緩存區的方式,比如輸出到屏幕的printf函數

無緩存:內容直接送到內核,用戶層沒有緩存區,比如write()函數

所以,把上面的printf(“printf123”)修改爲printf("write123\n"),

輸出結果就是:

printf123

write123

正經問題:

那麼將printf()函數的輸出目的地從終端屏幕改到一個文件“text”中,比如下面的代碼所做的


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(void){
        char *buf="123456\n";
        int fd,save_fd;
        char *lbuf;

        if((fd=open("text",O_RDWR|O_CREAT,0777))<0){ //打開一個文件 text
                perror("open");
                exit(1);
        }

        save_fd=dup(STDOUT_FILENO);        //使用save_fd保存stdout的文件描述符
        dup2(fd,STDOUT_FILENO);                 //將text的文件描述符複製給stdout,這樣,標準輸出就指向了text文件
        close(fd);        //關閉沒用的文件描述符fd

        printf("1\n");                //使用printf函數向text輸出 1
        write(STDOUT_FILENO,buf,strlen(buf));  //使用write函數向text寫入數據
        dup2(save_fd,STDOUT_FILENO);         //將文件描述符換回來,回覆標準輸入
        close(save_fd);

        printf("2\n");                  //向屏幕輸出 2
        write(STDOUT_FILENO,buf,strlen(buf)); //向屏幕輸出 1

        return 0;

}


屏幕輸出結果:

123456

1

2

文件text中的內容:

123456

對於上面的結果,有兩個問題:

第一個問題:

在改變標準輸出之後,stdou對應的應該是文件text,那麼printf("1\n")也應該文件text中打印一個 "1\n"纔對,但是卻沒有,而write()函數的內容是成功寫入了text,這就說明,標準輸出確實被修改成功了,

第二個問題:

再將標準輸出恢復之後,也就是執行了 dup2(save_fd,STDOUT_FILENO);  這條語句以後,printf("2\n") 並沒有立即向屏幕上打印數據,而是等write()之後纔打印了2 。並且這個2還是在1之後

分析:

printf()函數的緩存屬性發生了改變,也就是之前是行緩存,而現在變成了全緩存,那麼爲什麼不是無緩存呢,因爲如果是無緩存,那麼 "1\n"就因該被刷新到內核緩存區了,而write函數正是講“123456\n”從內核寫入文件的,如果“1\n”在內核緩存區,那麼他就應該被write函數的寫操作影響,或者沖掉,這樣來說,“1\n”就根本還沒有進入內核,這也就是爲什麼分析得出,printf函數的緩存性質發生了改變

爲了驗證上面的猜測:

使用setvbuf()函數設置stdou的緩存區屬性

setvbuf()的原函數

  #include <stdio.h>

 int setvbuf(FILE *stream, char *buf, int mode, size_t size);
stream 文件輸入流

buf 緩存區地址

mode 緩存區性質  

               _IONBF unbuffered  無緩存

              _IOLBF line buffered 行緩存

              _IOFBF fully buffered 全緩存
修改代碼,在第一次修改stdout之後,設置stdout的緩存區性質

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(void){
        char *buf="123456\n";
        int fd,save_fd;
        char *lbuf;

        if((fd=open("text",O_RDWR|O_CREAT,0777))<0){
                perror("open");
                exit(1);
        }

        save_fd=dup(STDOUT_FILENO);
        dup2(fd,STDOUT_FILENO);
        close(fd);
      //設置stdout的緩存區性質
     setvbuf(stdout,lbuf=malloc(512*sizeof(char)),_IOLBF,512);

        printf("1\n");
        write(STDOUT_FILENO,buf,strlen(buf));
        dup2(save_fd,STDOUT_FILENO);
        close(save_fd);

        printf("2\n");
        write(STDOUT_FILENO,buf,strlen(buf));

        return 0;

}

  從setvbuf函數的原函數來看,是使用文件的讀寫指針來設置緩存區屬性的,所以,當改變stdou的時候,纔會對printf()的緩存區造成影響

修改之後的輸出結果

屏幕:

2

123456


text文件中:

1

123456

所以,可以得出結果,第一次修改stdout的時候,確實改變了printf()的緩存性質,從第一次的輸出結果來看,第二次將stdout改回成標準輸出的時候,printf()的緩存性質並沒變回來(原因暫時不明),然後在設置了緩存區性質之後,從第二次的輸出結果可以得出,printf()緩存區又變成了行緩存

總結:

如果要使用dup2()函數改變文件的標準輸出,需要判斷緩存的性質是否是發生了變化,如果發生了變化,就需要重新設置,否則不能得到想要的結果

如果不改變標準輸出,在使用printf的時候,需要注意是否要隨行輸出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章