|
一般标准I/O 库函数带有缓冲机制,如 printf() 函数。
当标准输出连接到终端设备时,它是行缓冲的。也就是说,输出是一行一行的来,每一行由换行符进行冲洗缓冲,相当于输出一行后缓冲区就再没内容。
当标准输出重定向到一个文件时,它是全缓冲的,也就是说缓冲区中的内容虽然输出了,但是在缓冲区中的内容仍然保留,没有被冲洗掉。
下面是示例代码:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <stdlib.h>
void pr_stdio(const char *, FILE *);
int main(void)
{
FILE *fp;
fputs ("enter any character\n", stdout);
if (getchar() == EOF) {
perror("getchar");
exit (EXIT_FAILURE);
}
fputs("one line to standard error\n", stderr);
pr_stdio("stdin", stdin);
pr_stdio("stdout", stdout);
pr_stdio("stderr", stderr);
if ((fp = fopen("/etc/issue", "r")) == NULL) {
perror("fopen");
exit (EXIT_FAILURE);
}
if (getc(fp) == EOF) {
perror("getc");
exit (EXIT_FAILURE);
}
pr_stdio("/etc/issue", fp);
return (0);
}
void pr_stdio (const char *name, FILE *fp)
{
printf ("stream = %s, ", name);
if (fp->_IO_file_flags & _IO_UNBUFFERED)
printf("unbuffered");
else if (fp->_IO_file_flags & _IO_LINE_BUF)
printf("line buffered");
else
printf("fully bufferd");
printf(", buffer size = %d\n", fp->_IO_buf_end - fp->_IO_buf_base);
}
直接在终端上输出:
[root@centos C]# ./buffer
enter any character
one line to standard error
stream = stdin, line buffered, buffer size = 4096
stream = stdout, line buffered, buffer size = 4096
stream = stderr, unbuffered, buffer size = 1
stream = /etc/issue, fully bufferd, buffer size = 4096 进行文件重定向输出:[root@centos C]# cat std.out
enter any character
stream = stdin, fully bufferd, buffer size = 4096
stream = stdout, fully bufferd, buffer size = 4096
stream = stderr, unbuffered, buffer size = 1
stream = /etc/issue, fully bufferd, buffer size = 4096 第 1 次使三个标准流(stdin, stdout, stderr)与终端相连,第 2 次使它们重定向到文件。
在程序中,_IO_LINE_BUFFERED、_IO_buf_base、_IO_buf_end、_IO_UNBUFFERED 及 _IO_file_flags 在 libio.h 头文件中有定义,这个头文件在 stdio.h 中有包含。
从输出可以看到,标准错误 stderr 是非缓冲的。当标准输入,标准输出,标准错误与终端连接时,它们是行缓冲的,行缓冲的长度是 4096 (在老的内核上可能是1024)。需要注意的是,这并不是将标准输入和标准输出的行限制为 4096 个字节,这只是缓冲区的长度,如果将一个具有 8192 个字节的行输出,那么需要进行两次 write() 系统调用。
当将标准输入和标准输出流重定向到扑通文件时,它们就变成了全缓冲,它的长度也是 4096 个字节。普通文件在系统默认情况下都是全缓冲的。
从下面代码可以比较出行缓冲和全缓冲的区别:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int glob = 6;
char buf[] = "a write to stdout\n";
int main(void)
{
int var;
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf) - 1) {
perror ("write");
exit (EXIT_FAILURE);
}
printf ("before fork\n");
if ((pid = fork()) < 0) {
perror ("fork");
exit (EXIT_FAILURE);
} else if (pid == 0) {
glob++;
var++;
} else {
sleep(2);
}
printf ("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
也分为两次运行,第 1 次就在终端中直接运行,第 2 次仍然是重定向到文件中。
终端方式:[beyes@localhost syscall]$ ./fork
a write to stdout
before fork
pid = 2051, glob = 7, var = 89
pid = 2050, glob = 6, var = 88 因为是行缓冲,所以只打印 1 次 "before fork" 。
重定向到文件的输出:[beyes@localhost syscall]$ ./fork > temp.out
[beyes@localhost syscall]$ cat temp.out
a write to stdout
before fork
pid = 2062, glob = 7, var = 89
before fork
pid = 2061, glob = 6, var = 88
从第 2 次输出中看到有两次 before fork 的打印。第 1 个 "before fork" 是父进程所打印。如上面说的,重定向到文件是全缓冲的方式,所以在这一行内容仍保存在父进程的缓冲区中。接着,在 fork() 出子进程时,子进程要将父进程的数据空间复制到它自己的空间去,这时父进程的缓冲区也理所当然的被子进程复制。于是,父子进程都各自有了该行内容(before fork)的标准 I/O 缓冲区。这样一来,在子进程返回(return)之前子进程里所保存的 "before fork" 也就随之被冲出来。在每个进程终止时,最终会冲洗其缓冲区中的副本。
从:
pid = 2062, glob = 7, var = 89
before fork
这个输出顺序我们可以推断,缓冲区进行数据的缓冲也如同压栈出栈,后来的内容会在缓冲区头部。 |
|