5.12. Implementation DetailsAs we've mentioned, under the UNIX System, the standard I/O library ends up calling the I/O routines that we described in Chapter 3. Each standard I/O stream has an associated file descriptor, and we can obtain the descriptor for a stream by calling fileno.
We need this function if we want to call the dup or fcntl functions, for example. To look at the implementation of the standard I/O library on your system, start with the header <stdio.h>. This will show how the FILE object is defined, the definitions of the per-stream flags, and any standard I/O routines, such as getc, that are defined as macros. Section 8.5 of Kernighan and Ritchie [1988] has a sample implementation that shows the flavor of many implementations on UNIX systems. Chapter 12 of Plauger [1992] provides the complete source code for an implementation of the standard I/O library. The implementation of the GNU standard I/O library is also publicly available. ExampleThe program in Figure 5.11 prints the buffering for the three standard streams and for a stream that is associated with a regular file. Note that we perform I/O on each stream before printing its buffering status, since the first I/O operation usually causes the buffers to be allocated for a stream. The structure members _IO_file_flags, _IO_buf_base, and _IO_buf_end and the constants _IO_UNBUFFERED and _IO_LINE_BUFFERED are defined by the GNU standard I/O library used on Linux. Be aware that other UNIX systems may have different implementations of the standard I/O library. If we run the program in Figure 5.11 twice, once with the three standard streams connected to the terminal and once with the three standard streams redirected to files, we get the following result: $ ./a.out stdin, stdout, and stderr connected to terminal enter any character we type a newline one line to standard error stream = stdin, line buffered, buffer size = 1024 stream = stdout, line buffered, buffer size = 1024 stream = stderr, unbuffered, buffer size = 1 stream = /etc/motd, fully buffered, buffer size = 4096 $ ./a.out < /etc/termcap > std.out 2> std.err run it again with all three streams redirected $ cat std.err one line to standard error $ cat std.out enter any character stream = stdin, fully buffered, buffer size = 4096 stream = stdout, fully buffered, buffer size = 4096 stream = stderr, unbuffered, buffer size = 1 stream = /etc/motd, fully buffered, buffer size = 4096 We can see that the default for this system is to have standard input and standard output line buffered when they're connected to a terminal. The line buffer is 1,024 bytes. Note that this doesn't restrict us to 1,024-byte input and output lines; that's just the size of the buffer. Writing a 2,048-byte line to standard output will require two write system calls. When we redirect these two streams to regular files, they become fully buffered, with buffer sizes equal to the preferred I/O sizethe st_blksize value from the stat structurefor the file system. We also see that the standard error is always unbuffered, as it should be, and that a regular file defaults to fully buffered. Figure 5.11. Print buffering for various standard I/O streams#include "apue.h" void pr_stdio(const char *, FILE *); int main(void) { FILE *fp; fputs("enter any character\n", stdout); if (getchar() == EOF) err_sys("getchar error"); fputs("one line to standard error\n", stderr); pr_stdio("stdin", stdin); pr_stdio("stdout", stdout); pr_stdio("stderr", stderr); if ((fp = fopen("/etc/motd", "r")) == NULL) err_sys("fopen error"); if (getc(fp) == EOF) err_sys("getc error"); pr_stdio("/etc/motd", fp); exit(0); } void pr_stdio(const char *name, FILE *fp) { printf("stream = %s, ", name); /* * The following is nonportable. */ if (fp->_IO_file_flags & _IO_UNBUFFERED) printf("unbuffered"); else if (fp->_IO_file_flags & _IO_LINE_BUF) printf("line buffered"); else /* if neither of above */ printf("fully buffered"); printf(", buffer size = %d\n", fp->_IO_buf_end - fp->_IO_buf_base); } |