The Two Standard Output Streams - Stdout and Stderr: Next
The Two Standard Output Streams - Stdout and Stderr: Next
Systems Programming
Pedantically, printf() actually sends its output to the stdout (pronounced standard-output) stream which, by default, is connected to the screen, but may be
redirected to a file or even to another program (using operating system features, not C features).
Here, our program "doesn't care" where its output is going - to the default location (the screen), to a file, or through a pipe.
In our programs, we could choose to explicitly send to the stdout stream, instead of printf(), with:
stdout and stderr are two of the standard I/O streams that are created and initialized by the C runtime system when the execution of new C programs is
commenced.
In general, we prefer to write "normal" output to stdout, and errors to stderr, and we'll adopt this practice in the rest of the unit.
The standard perror() function (from Lecture-10) also sends its output to stderr.
This is standard mechanism by which a single program receives its input via a named file, the contents of a redirected file, or through a pipe. We describe
such a program as a filter [see The Art of Unix Programming, ch07]:
#include <stdio.h>
// process() READS FROM A FILE-POINTER, REGARDLESS OF ITS SOURCE
int process(FILE *infp)
{
int result = 0;
....
return result;
}
int main(int argc, char *argv[])
{
int result;
printf("program's name is %s\n", argv[0]);
// NO ARGUMENTS PROVIDED, ACCEPT DEFAULT (STANDARD) INPUT
if(argc == 1) {
result = process(stdin);
}
else {
// FILE NAME ARGUMENTS PROVIDED, READ FROM EACH OF THEM
for(int a=1 ; a<argc ; ++a) {
FILE *infp = fopen(argv[a], "r");
if(infp == NULL) {
fprintf(stderr, "%s: cannot open %s\n", argv[0], argv[a]);
exit(EXIT_FAILURE);
}
result = process(infp);
fclose(infp); // WE OPENED, SO WE CLOSE IT
}
}
return result;
}
Unix-based operating systems provide file descriptors, simple integer values, to identify 'communication channels' - such as files, interprocess-
communication pipes, (some) devices, and network connections (sockets).
C99 defines the FILE * datatype (the file-pointer) as an abstraction over file descriptors to manage (simplify?) access to descriptors.
(As we know) when calling C99 standard functions we provide, or receive, a file-pointer. When calling OS system-calls we provide, or receive, a file-
descriptor.
Buffered Input
When reading from a file, the code for using file pointers and file descriptors can be very similar.
However, the standard I/O streams perform input buffering - the data read from a file is actually read into a large memory buffer (owned by the user's
process and 'remembered' by the file-pointer) and then 'dished out' to the requesting application:
Remember that the read() function is a system-call, and that system-calls can be expensive because it permits the operating system to reschedule the
requesting process from the RUNNING state to the BLOCKED state if the data is not ready.
Consider what may happen in the above code if the value of MYSIZE is not always 10000, but is 1, 10, 1000, or 100000.
Buffered Output
Similarly, a big distinction between stdout and stderr is that the former is buffered (for efficiency), while the latter is unbuffered (to ensure output appears
immediately).
A consequence of this is that, if all of your output is sent to stdout and your program crashes, you may not see all of your output.
We've noticed that the main() function receives command-line arguments from its calling environment (usually the operating system):
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("program's name: %s\n", argv[0] );
for(int a=0 ; a < argc ; ++a)
{
printf("%i: %s\n", a, argv[a] );
}
return 0;
}
We know:
While we typically associate argv with strings, we remember that C doesn't innately support strings. It's only by convention or assumption that we may
assume that each value of argv[i] is a pointer to something that we'll treat as a string.
In the previous example, we print "from" the pointer. Alternatively, we can print every character in the arguments:
#include <stdio.h>
int main(int argc, char *argv[])
{
for(int a=0 ; a < argc ; ++a)
{
printf("%i: ", a);
The operating system actually makes argv much more usable, too:
each argument is guaranteed to be terminated by a null-byte (because they are strings), and
the argv array is guaranteed to be terminated by a NULL pointer.
For programs on Unix-derived systems (such as Apple's OS-X and Linux), these are termed command switches, and their introductory character is a
hyphen, or minus.
Keep in mind, too, that many utilities appear to accept their command switches in (almost) any order. For the common ls program to list files, each of these
is equivalent:
ls -l -t -r files
ls -lt -r files
ls -ltr files
ls -rtl files
Of note, neither the operating system nor the shell know the switches of each program, so it's up to every program to detect them, and report any
problems.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
char *progname;
bool dflag = false;
int main(int argc, char *argv[])
{
progname = argv[0];
--argc; ++argv;
while(argc > 0 && (*argv)[0] == '-') // or argv[0][0]
{
if((*argv)[1] == 'd') // or argv[0][1]
dflag = !dflag;
else
argc = 0;
--argc; ++argv;
}
if(argc < 0)
{
fprintf(stderr, "Usage : %s [-d] [filename]\n", progname);
exit(EXIT_FAILURE);
}
if(argc > 0)
{
while(argc > 0)
{
process_file(*argv); // provide filename to function
--argc; ++argv;
}
}
else {
process_file(NULL); // no filename, use stdin or stdout?
}
return 0;
}
In addition, command switches do not just indicate, or toggle, a Boolean attribute to the program, but often provide additional string values and numbers to
further control the program.
To simplify the task of processing command switches in our programs, we'll use the function getopt().
getopt() is not a function in the Standard C library but, like the function strdup(), it is widely available and used. In fact, getopt() conforms to a different
standard - an POSIX standard [2], which provides functions enabling operating system portability.
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <getopt.h>
#define OPTLIST "d"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#define OPTLIST "df:n:"
getopt sets the global pointer variable optarg to point to the actual value provided after -n - regardless of whether any spaces appear between the switch
and the value.