Arthur J. O'Dwyer

CMU 15-113: Safe I/O and the getline function

This directory contains the following files:

To use the getline_113 function, you'll need to download the getline.h and getline.c files, and #include "getline.h" in any C file you write that uses the function.

The .h file provides the prototypes for two functions: getline_113 and fgetline_113. The getline_113 function is actually just fgetline_113 acting on the standard input stream instead of on a client-provided FILE*. (The "client" is you, the programmer, as distinguished from me, the programmer who wrote getline in the first place; and as distinguished from the "user," who's running your program.)

The fgetline_113 function is based on fgets. (Type "man fgets" at a Unix prompt to see the prototype and documentation for that standard library function.) The essential difference between fgets and fgetline_113 is that fgets needs the client to provide a pre-allocated buffer and that buffer's length; fgetline_113 just asks the client for a reference to a char* variable. That variable will get assigned to point to a chunk of dynamically allocated memory, obtained from malloc, into which the fgetline function will read its data. The chunk will get resized automatically by fgetline_113 as the line gets longer and longer, until a newline ('\n') is spotted. Then, as with fgets, input is halted and the whole line, together with the newline character on the end and the terminating '\0', is returned to the client.

Well, actually, fgetline_113 and getline_113 don't include the newline character in the resulting string anymore. They strip it out, because in practice that's what you'd want to do most of the time anyway. If getting the input exactly the way the user typed it — newlines, trailing whitespace, and all — is important to you, then you can use fgetline_notrim instead of fgetline_113. It works exactly the same way; in fact, fgetline_113 itself calls fgetline_notrim, and then calls another function to strip off the newline!

As with fgets, fgetline_113 returns a pointer to the buffer on success, and NULL on failure. Failure can happen in a bunch of different ways, which are all documented in getline.c. In practice, you can probably treat any error as "end-of-file," but if you want more specific information about how to test for particular errors, see the sample program getline_test.c.

Menu character input

This isn't directly related to getline_113, but it's related to safe input. If you want to get a single character from the user, and discard the rest of the line, just use the following magic formula:

    char ch;
    scanf(" %c%*[^\n]", &ch); getchar();
(Notice the space before the first percent sign. You should cut and paste this code, rather than trying to type it in yourself, unless you're 100 percent sure you will be able to tell if you made a typo.) It will store the first entered non-whitespace character in ch, and discard everything up to and including the next newline character. The scanf call itself will return either 1 or EOF, depending on whether a character was successfully read. You don't need to worry about how the magic formula works, but if you want a challenge, you can figure it out by logical reasoning; everything you need to know can be found via man scanf on a Unix machine.

Free what you malloc

Remember, getline_113 and fgetline_113 use malloc to create their input buffers, so once you're done using the buffer, you need to free it! A minimal use of getline_113 should look something like this:

    char *line;
    if (getline_113(&line) == NULL) {
        puts("End of file or error encountered");
    else {
        /* do something with the line */
    free(line);      /* This line is very important! */
fgetline_113 will never set line to an invalid pointer value, or one that can't be safely freed by the client.

Double freeing

But remember: You can only free a chunk once! Don't try to free the same chunk of memory twice. The following code has a bug:

    char *line;
    int done = 0;
    while (!done && getline_113(&line) != NULL) {
        if (strcmp(line, "load") == 0) loadSomething();
        else if (strcmp(line, "save") == 0) saveSomething();
        else if (strcmp(line, "quit") == 0) done = 1;
        else puts("I don't understand you.");
What's more, removing either of the two calls to free won't fix the bug! You'd have to change the code to something more like this:
    char *line;
    while (getline_113(&line) != NULL) {
        if (strcmp(line, "load") == 0) loadSomething();
        else if (strcmp(line, "save") == 0) saveSomething();
        else if (strcmp(line, "quit") == 0) break;
        else puts("I don't understand you.");

Important note about using GCC

I had originally named these two functions getline and fgetline, instead of getline_113 and fgetline_113. However, this caused a usability problem that was so interesting I decided to change their names:

    % gcc -O2 -W -Wall -ansi -pedantic echo.c
    % ./a.out
    Segmentation fault
The mistake in that snippet is that the programmer forgot to include getline.c on the command line when he invoked the compiler! Amazingly, GCC doesn't complain about a missing function definition when that function has the same name as one of the GNU C library's default functions, and there is indeed a function called getline in the GNU C library. Unfortunately for us, GNU's getline doesn't have the same interface as (the newly renamed) getline_113, so we have mistakenly invoked undefined behavior!

The simple solution, obviously, is to make sure every student in the class always invokes the compiler correctly, including getline.c when it's needed. But I don't think that's likely to happen, so I've just changed the name of the offending function. Now, if you forget getline.c, you'll see a compiler warning:

    % gcc -W -Wall -ansi -pedantic -O2 echo.c
    /tmp/cckl9W2g.o: In function `main':
    /tmp/cckl9W2g.o(.text+0x24): undefined reference to `getline_113'
    collect2: ld returned 1 exit status

Reporting bugs

If you think you have found a bug in fgetline_notrim, please report it to me, Arthur O'Dwyer (ajo@). I'll try to fix the bug, or explain why it's not one, as quickly as possible.

This page was last updated    24 January 2006
All original code, images and documentation on this page are in the public domain.