THE STDIO.H HEADER FILE
Example program ------> SIMPLEIO.C
Examine the file SIMPLEIO.C for our first look at a file with standard I/O. Standard I/O refers to the places where most data is either read from, the keyboard, or written to, the video monitor. Since they are used so much, they are used as the default I/O devices and do not need to be named in the Input/Output instructions. This will make more sense when we actually start to use them so let's look at the file in front of you.
The first thing you should take notice of is the second line of the example file, the line with #include <stdio.h>. This is very much like the #define we have already studied, except that instead of a simple substitution, an entire file is read in at this point. The system will find the file named stdio.h and read its entire contents in, replacing this statement. Obviously then, the file named stdio.h must contain valid C source statements that can be compiled as part of a program. You will recall that we stated earlier that the preprocessor does textual substitution. This particular file is composed of several standard #defines and prototypes to define some of the standard I/O operations. The file is called a header file and you will find several different header files on the source disks that came with your C compiler. Each of the header files has a specific purpose and any or all of them can be included in any program. Most header files contain definitions of a few types, function prototypes for the functions in its group, and some macros.
Your C compiler uses the double quote marks to indicate that the search for the include file will begin in the current directory, and if it not found there, the search will continue in the include directory as set up in the environment for your compiler. It also uses the "less than" and "greater than" signs to indicate that the file search should begin in the directory specified in the environment. Most of the programs in this tutorial use the "<" and ">" in the include statements. The next program uses the double quotes to illustrate the usage. Note that this will result is a slightly slower (but probably unnoticeable) compilation because the system will search the current directory first. If you know the include file is not in the current directory, it is best to use the "<" and ">" with the filename.
As many includes can be used as necessary, and it is perfectly all right for one header file to include one or more additional header files. It is very common to include four or five header files in a program.
It would be a profitable exercise for you to inspect the header file limits.h at this time for a complete definition of the sizes of all simple variables on your system. You should be able to understand most of this file by this point in your study of C.
INPUT/OUTPUT OPERATIONS IN C
Actually the C programming language has no input or output operations defined as part of the language, they must be user defined. Since everybody does not want to reinvent his own input and output operations, the compiler writers have done a lot of this for us and supplied us with several input functions and several output functions to aid in our program development. The functions have become a standard, and you will find the same functions available in every compiler. In fact, the industry standard of the C language definition has become the book written by Kernigan and Ritchie, and they have included these functions in their definition.
Occasionally, when reading literature about C, you will find an author refer to K & R. This refers to the book, "The C Programming Language", written by Kernigan and Ritchie. You would be advised to purchase a copy for reference. The second edition of this book is available and is definitely the preferred edition. Note that the book by Kernigan and Ritchie does not cover the ANSI-C standard, but it is still the preferred book for a general reference to the C programming language. The major item that is not covered is the use of prototypes, but you can easily integrate your knowledge of prototypes into the concise descriptions of other C constructs given in this book. Consider it a source of information after you gain some experience with C because it is not a very good book to learn the language from.
You should print out the file named stdio.h and spend some time studying it. There will be a lot that you will not understand about it, but parts of it will look familiar. The name stdio.h is sort of cryptic for "standard input/output header", because that is exactly what it is. It defines the standard input and output functions in the form of #defines, macros, and prototypes for the functions. Don't worry too much about the details of this now. You can always return to this topic later for more study if it interests you, but you will really have no need to completely understand the STDIO.H file. You will have a tremendous need to use it however, so these comments on its use and purpose are necessary.
OTHER INCLUDE FILES
When you begin writing larger programs and splitting them up into separately compiled portions, you will have occasion to use some definitions common to each of the portions. It would be to your advantage to make a separate file containing the definitions and use the #include to insert it into each of the files. If you want to change any of the common statements, you will only need to change one file and you will be assured of having all of the common statements agree. This is getting a little ahead of ourselves but you now have an idea how the #include directive can be used with your own files.
BACK TO THE FILE NAMED SIMPLEIO.C
Let's continue our tour of the file in question. The one variable named c is defined and a message is printed out with the familiar printf() function. We then find ourselves in a continuous loop as long as the value of c is not equal to capital X. If there is any question in your mind about the loop control, you should review chapter 3 before continuing. The two new functions within the loop are of paramount interest in this program since they are new functions to us. These are functions to read a character from the keyboard and display a character on the monitor.
The function getchar() reads a single character from the standard input device, the keyboard, and assigns it to the variable named c. The next function putchar(), uses the standard output device, the video monitor, and outputs the character contained in the variable named c. The character is output at the current cursor location and the cursor is advanced one space for the next character. The system is therefore taking care of a lot of the overhead for us. The loop continues reading and displaying characters until we type a capital X which terminates the loop.
Compile and run this program for a few surprises. When you type on the keyboard, you will notice that what you type is displayed faithfully on the screen, and when you hit the return key, the entire line is repeated. We only told it to output each character once but it seems to be saving the characters up and redisplaying them. A short explanation is in order.
THE OPERATING SYSTEM IS HELPING US OUT
We need to understand a little bit about how the operating system works to understand what is happening here. When data is read from the keyboard, under control of the operating system, the characters are stored in a buffer until a carriage return is entered at which time the entire string of characters is given to the program. When the characters are being typed, however, the characters are displayed one at a time on the monitor. This is called echo, and happens in many of the applications you run.
With the above paragraph in mind, it should be clear that when you are typing a line of data into SIMPLEIO, the characters are being echoed by the operating system, and when you return the carriage by hitting return or enter, the characters are given to the program. As each character is given to the program, it displays it on the screen resulting in a repeat of the line typed in. To better illustrate this, type a line with a capital X somewhere in the middle of the line. You can type as many characters as you like following the X and they will all display because the characters are being read in by the operating system, echoed to the monitor, and placed in the input buffer. The operating system doesn't think there is anything special about a capital X. When the string is given to the program, however, the characters are accepted by the program one at a time and sent to the monitor one at a time, until a capital X is encountered. After the capital X is displayed, the loop is terminated, and the program is terminated. The characters on the input line following the capital X are not displayed because the capital X signalled program termination.
Compile and run SIMPLEIO.C. After running the program several times and feeling confident that you understand the above explanation, we will go on to another program.
Don't get discouraged by the above seemingly weird behavior of the I/O system. It is strange, but there are other ways to get data into the computer. You will actually find the above method useful for many applications, and you will find some of the following useful also.
ANOTHER STRANGE I/O METHOD
Example program ------> SINGLEIO.C
Load the file named SINGLEIO.C and display it on your monitor for another method of character I/O. Once again, we start with the standard I/O header file using the double quote method of defining it. Then we define a variable named c, and we print a welcoming message. Like the last program, we are in a loop that will continue to execute until we type a capital X, but the action is a little different here.
Note that conio.h and the _getch() function described below are not a part of the ANSI-C standard but are available on most C compilers written for DOS.
The function named _getch() is a get character function. It differs from the function named getchar() in that it does not get tied up in DOS. It reads the character in without echo, and puts it directly into the program where it is operated on immediately. This function therefore reads a character, immediately displays it on the screen, and continues the operation until a capital X is typed. Note that although _getch() is available with most popular microcomputer C compilers, it is not included in the ANSI standard and may not be available with all C compilers. It's use may therefore make a program nonportable. If your compiler does not support the _getch() function, you can simply ignore this example program.
When you compile and run this program, you will find that there is no repeat of the lines when you hit a carriage return, and when you hit the capital X, the program terminates immediately. No carriage return is needed to get it to accept the line with the X in it, so this program operates a little differently from the last one. However, we do have another problem here, since there is no linefeed with the carriage return.
NOW WE NEED A LINE FEED
Example program ------> BETTERIN.C
It is not apparent to you in most application programs but when you hit the enter key, the program supplies a linefeed to go with the carriage return. You need to return to the left side of the monitor and you also need to drop down a line. The linefeed is not automatic. We need to improve our program to do this also. If you will load and display the program named BETTERIN.C, you will find a change to incorporate this feature.
In BETTERIN.C, we have two additional statements at the beginning that will define the character codes for the linefeed (LF), and the carriage return (CR). If you look at any ASCII table you will find that the codes 10 and 13 are exactly as defined here. In the main program, after outputting the character in line 16, we compare it to CR, and if it is equal to CR, we also output a linefeed which is the LF. We could have completely omitted the two #define statements and used the statement if (c == 13) putchar(10); but it would not be very descriptive of what we are doing here. The method used in this program represents better programming practice.
You will notice that line 17 deviates from the usual style for an if statement, but we have a choice. We can format the code any way we desire to improve the readability. It is strictly a programmer's choice.
Compile and run BETTERIN.C to see if it does what we have said it should do. It should display exactly what you type in, including a linefeed with each carriage return, and should stop immediately when you type a capital X. If your compiler does not support _getch(), use the getchar() function.
WHICH METHOD IS BEST?
We have examined two methods of reading characters into a C program, and are faced with a choice of which one we should use. It really depends on the application because each method has advantages and disadvantages.
When using the first method, the operating system is actually doing all of the work for us by storing the characters in an input buffer and signaling us when a full line has been entered. We could write a program that, for example, did a lot of calculations, then went to get some input. While we were doing the calculations, the operating system would be accumulating a line of characters for us, and they would be there when we were ready for them. However, we could not read in single keystrokes because the operating system would not report a buffer of characters to us until it recognized a carriage return.
The second method, used in BETTERIN.C, allows us to get a single character, and act on it immediately. We do not have to wait until the operating system decides we can have a line of characters. We cannot do anything else while we are waiting for a character because we are waiting for the input keystroke and tying up the entire machine. This method is useful for highly interactive types of program interfaces. It is up to you as the programmer to decide which is best for your needs.
I should mention at this point that there is also an _ungetch() function that works with the _getch() function and is also not a part of the ANSI-C standard library, but is available with most DOS compilers. If you _getch() a character and find that you have gone one too far, you can _ungetch() it back to the input device. This simplifies some programs because you don't know that you don't want the character until you get it. You can only _ungetch() one character back to the input device, but that is sufficient to accomplish the task this function was designed for. It is difficult to demonstrate this function in a simple program so its use will be up to you to study when you need it. Another function that may be available with your compiler, but is not part of the ANSI standard, is the _getche() function which is identical to the _getch() function except that it echoes the character to the monitor for you.
The discussion so far in this chapter should be a good indication that, while the C programming language is very flexible, it does put a lot of responsibility on you as the programmer to keep many details in mind.
NOW TO READ IN SOME INTEGERS
Example programs ------> INTIN.C
Load and display the file named INTIN.C for an example of reading some formatted data from the keyboard. The structure of this program is very similar to the last three except that we define an int type variable and loop until the variable somehow acquires the value of 100.
Instead of reading in a character at a time, as we have in the last three example programs, we read in an entire integer value with one call using the function named scanf(). This function is very similar to the printf() that you have been using for quite some time by now except that it is used for input instead of output. Examine the line with the scanf() and you will notice that it does not ask for the variable valin directly, but gives the address of the variable since it expects to have a value returned from the function. Recall that a function must have the address of a variable in order to return a value to that variable in the calling program. Failing to supply a pointer to the parameter in the scanf() function is the most common problem encountered in using this function.
The function scanf() scans the input line until it finds the first data field. It ignores leading blanks and in this case, it reads integer characters until it finds a blank or an invalid decimal character, at which time it stops reading and returns the value.
Remembering our discussion above about the way the input buffer works, it should be clear that nothing is actually acted on until a complete line is entered and it is terminated by a carriage return. At this time, the buffer is input, and our program will search across the line reading all integer values it can find until the line is completely scanned. This is because we are in a loop and we tell it to find a value, print it, find another, print it, etc. If you enter several values on one line, it will read each one in succession and display the values. Entering the value of 100 will cause the program to terminate, and entering the value 100 with other values following, will cause termination before the following values are considered.
IT MAKES WRONG ANSWERS SOMETIMES
If your system uses a 2 byte integer and you enter a number up to and including 32767, it will display correctly, but if you enter a larger number, it will appear to make an error. For example, if you enter the value 32768, it will display the value of -32768, entering the value 65536 will display as a zero. These are not errors but are caused by the way an int variable is defined. The most significant bit of the 16 bit pattern available for the integer variable is the sign bit, so there are only 15 bits left for the value. The variable can therefore only have the values from -32768 to 32767, any other values are outside the range of integer variables. This is up to you to take care of in your programs. It is another example of the increased responsibility you must assume using C rather than another high level language such as Pascal, Modula-2, etc.
The above paragraph is true for 16 bit C compilers. There is an ever increasing possibility that your compiler uses an integer value stored in a field size larger than 16 bits. If that is the case, the same principles will be true but with different limits than those given above.
Compile and run this program, entering several numbers on a line to see the results, and with varying numbers of blanks between the numbers. Try entering numbers that are too big to see what happens, and finally enter some invalid characters to see what the system does with nondecimal characters.
CHARACTER STRING INPUT
Example program ------> STRINGIN.C
Load and display the file named STRINGIN.C for an example of reading a string variable from the keyboard. This program is identical to the last one except that instead of an integer variable, we have defined a string variable with an upper limit of 24 characters (remember that a string variable must have a null character at the end). The variable in the scanf() does not need an & because big is an array variable and by definition it is already a pointer. This program should require no additional explanation. Compile and run it to see if it works the way you expect.
You probably got a surprise when you ran it because it separated your sentence into separate words. When used in the string mode of input, scanf() reads characters into the string until it comes to either the end of a line or a blank character. Therefore, it reads a word, finds the blank following it, and displays the result. Since we are in a loop, this program continues to read words until it exhausts the input buffer. We have written this program to stop whenever it finds a capital X in column 1, but since the sentence is split up into individual words, it will stop anytime a word begins with capital X. Try entering a 5 word sentence with a capital X as the first character in the third word. You should get the first three words displayed, and the last two simply ignored when the program stops.
Try entering more than 24 characters to see what the program does. In an actual program, it is your responsibility to count characters and stop when the input buffer is full. You may be getting the feeling that a lot of responsibility is placed on you when writing in C. Along with this responsibility you get a lot of flexibility in the bargain also. Because scanf() has no way to stop when the input array is full, it should not be used for string input in a quality program. It was used here only as an illustration of input programming.
INPUT/OUTPUT PROGRAMMING IN C
C was not designed to be used as a language for lots of input and output, but as a systems language where a lot of internal operations are required. You would do well to use another language for I/O intensive programming, but C could be used if you desire. The keyboard input is very flexible, allowing you to get at the data in a very low level way, but very little help is given you. It is therefore up to you to take care of all of the bookkeeping chores associated with your required I/O operations. This may seem like a real pain in the neck, but in any given program, you only need to define your input routines once and then use them as needed. Don't let this worry you. As you gain experience with C, you will easily handle your I/O requirements.
One final point must be made about these I/O functions. It is perfectly permissible to intermix scanf() and getchar() functions during read operations. In the same manner, it is also fine to intermix the output functions, printf() and putchar() in any way you desire.
IN MEMORY I/O
Example program ------> INMEM.C
The next operation may seem a little strange at first, but you will probably see lots of uses for it as you gain experience. Load the file named INMEM.C and display it for another type of I/O, one that never accesses the outside world, but stays in the computer.
In INMEM.C, we define a few variables, then assign some values to the ones named numbers for illustrative purposes and then use an sprintf() function. The function acts just like a normal printf() function except that instead of printing the line of output to a device, it prints the line of formatted output to a character string in memory. In this case the string goes to the string variable named line, because that is the string name we inserted as the first argument in the sprintf() function. The spaces after the 2nd %d were put there to illustrate that the next function will search properly across the line. We print the resulting string and find that the output is identical to what it would have been by using a printf() instead of the sprintf() in the first place. You will see that when you compile and run the program shortly.
Since the generated string is still in memory, we can now read it with the function sscanf(). We tell the function in its first argument that line is the string to use for its input, and the remaining parts of the line are exactly what we would use if we were going to use the scanf() function and read data from some input device. Note that it is essential that we use pointers to the data because we want to return data from a function. Just to illustrate that there are different ways to declare a pointer, two methods are used, but all are ultimately pointers. The first two simply declare the address of the elements of the array, while the last three use the fact that result, without the accompanying subscript, is a pointer. Just to keep it interesting, the values are read back in reverse order. Finally, the values are displayed on the monitor.
IS THAT REALLY USEFUL?
It seems sort of silly to read input data from within the computer but it does have a real purpose. It is possible to read data from an input device using any of the standard functions and then do a format conversion in memory. You could read in a line of data, look at a few significant characters, then use these formatted input routines to reduce the line of data to internal representation. That would sure beat writing your own data formatting routines.
STANDARD ERROR OUTPUT
Example program ------> SPECIAL.C
Sometimes it is desirable to redirect the output from the standard output device to a file. However, you may still want the error messages to go to the standard output device, in our case the monitor. This next function allows you to do that. Load and display SPECIAL.C for an example of this new function.
The program consists of a loop with two messages output, one to the standard output device and the other to the standard error device. The message to the standard error device is output with the function fprintf() and includes the device name stderr as the first argument. Other than those two small changes, it is the same as our standard printf() function. (You will see more of the fprintf() function in the next chapter, but its operation fit in better as a part of this chapter.) Ignore the line with the exit for the moment, we will return to it.
Compile and run this program, and you will find 12 lines of output on the monitor. To see the difference, run the program again with redirected output to a file named STUFF by entering the following line at the operating system prompt;
C:> special >stuff
This time you will only get the 6 lines output to the standard error device, and if you look in your directory, you will find that the file named STUFF contains the other 6 lines, those to the standard output device. You can use I/O redirection with any of the programs we have run so far, and as you may guess, you can also read from a file using I/O redirection but we will study a better way to read from a file in the next chapter. More information about I/O redirection can be found in your operating system manual.
WHAT ABOUT THE exit(4) STATEMENT?
Now to keep our promise about the exit(4) statement. Redisplay the file named SPECIAL.C on your monitor. The last statement exits the program and returns the value of 4 to the operating system. Any number within a predefined range can be used within the parentheses for communication with your operating system. If you are operating in a DOS environment and executing code in a BATCH file, this number can be tested with the ERRORLEVEL command.
Most compilers that operate in several passes return a 1 with this mechanism to indicate that a fatal error has been detected and it would be a waste of time to go on to another compilation pass resulting in even more errors. A return value of 0 would indicate that no error was detected.
Return to Table of Contents
Advance to Chapter 10