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