Safe Coding Practices – getchar() and putchar()

I confess that I get sloppy with getchar() and putchar(). These are macros, not functions, but the issue is that their return value is an int, not a char variable. The reason why is important if you want to follow safe coding practices.

The getchar() and putchar() functions return an int is because they’re stream-oriented and can interact with files. Keyboard input provides char values, but file input can generate int values, such as the EOF or End of File value, which is an int. And file output can generate int values on file errors. Safe coding practices dictate that you consider these conditions.

The following code is a simple I/O filter:

#include <stdio.h>
  
int main()
{
    int ch;

    while(1)
    {
        ch = getchar();
        putchar(ch);
    }

    return(0);
}

Both getchar() and putchar() use int variable ch, which is proper. If you run the program, you see everything input echoed back to output, as in:

hello
hello

If you type the EOF character, however, you get unpredictable results. EOF is Ctrl+D in most Unix shells, Ctrl+Z in Windows.

Further, if you use the filter to pipe file input, you see all sorts of wackiness:

cat file.txt | getputfilter

Above, file.txt is a text file and getputfilter is the program compiled with the source code example. If file.txt exists, the program doesn’t stop because the EOF is never tested for in the code. If file.txt doesn’t exist, the program does whatever as the non-existent file error isn’t captured. Obviously, the code needs improvements — it needs some safe coding practices implemented.

First, to test for the EOF, I modified the code as shown below:

#include <stdio.h>
  
int main()
{
    int ch;

    while(1)
    {
        ch = getchar();
        if( ch == EOF)
        {
            if( ferror(stdin) )
            {
                puts("\nEnd of File");
                clearerr(stdin);
                return(0);
            }
        }
        putchar(ch);
    }

    return(0);
}

The if(ch==EOF) statement tests for the EOF character. When EOF is encountered, the feof() function confirms it, specifically for the stdin “file.” If so, the text End of File is output, the error is cleared for standard input, and the program terminates with a return statement.

After you make the changes and compile the code, the program accurately spies the EOF character and doesn’t run amok.

You can further bolster the code by using a ferror(stdin) statement. Refer to my Lessons on the errno variable for more details on what you can do to further capture file I/O errors for getchar() and putchar().

As with getchar(), the putchar() function can suffer from file errors as well, specifically with regards to redirected output that may go to an invalid file. I demonstrate an example in next week’s Lesson.

4 thoughts on “Safe Coding Practices – getchar() and putchar()

  1. I believe EOF is usually -1 but can be any negative value. According to the excellent C in a Nutshell by Peter Prinz “char is synonymous with either signed char or unsigned char depending on the compiler” which means of course you cannot rely on char being able to accommodate EOF. (I use gcc which by default has signed chars.) It’s a pity ASCII doesn’t have an EOF value.

    (While checking up on this for my comment I found someone on stackoverflow saying that EOF couldn’t have an ASCII value because then you wouldn’t be able to read past the first occurrence of EOF. Stackoverflow is useful at times but you have to learn to ignore the large number of stupid comments.)

  2. It’s not just -1, but a int-sized -1. The -1 char value could also be 255 unsigned, which may appear as a character on some systems. So the return value definitely needs to be handled as an int if you want to capture that EOF.

    Also, (and I’m writing this for others who may read these comments), the EOF constant is defined as a specific value which is best treated as the constant EOF and not the value itself. For example, when running this code on a Unix terminal, I would type Ctrl+D to terminate. In Windows, I’d type Ctrl+Z. Same code, but interpreted differently.

  3. A function (or actually a macro as you point out) which mixes chars and ints isn’t really ideal but I cannot think of a way of getting round it.

    I am not too clear on why we need both
    if( ch == EOF)
    and
    if( feof(stdin) )

  4. The feof() is a typo. Glad you caught it! In the text it says “ferror(stdin),” which is what I meant to put in the code. I have fixed the typo in the code above.

Leave a Reply