使用write()函数和printf()函数输出一个字符串到终端

来源:互联网 发布:软件的缺陷等级 编辑:程序博客网 时间:2024/06/05 14:19

引子:

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的时候,需要注意是否要随行输出。

0 0