Introduction
Announcements

Schedule
Labs
Assignments
TA office hours

Tests, exam

Topic videos
Some course notes
Extra problems
Lecture recordings

Discussion board

Grades so far

Files in C

In unix, the basic system calls for dealing with files are: In unix we often don't have to call open() or close().
Three files are already open when our process is started (stdin, stdout, and stderr); and all files are closed automatically when our process exits.

But we don't usually use the above system calls for routine file access from C in unix. We want
1) something portable to non-unix; i.e. to run in any C environment, not just unix
2) buffering. It turns out that we often want to process data in our program in small pieces, and it is noticeably slower, even on a modern fast computer, to do a system call for each little piece of data. The portable package built on top of these unix system calls will read a large chunk of the file even if we've only called for one byte; it will save those not-yet-called-for bytes in a "buffer" until we call for them later.

This is the stdio package, whose name we've seen in writing #include <stdio.h>.

Stdio

getchar(): get one character from the standard input. Returns an int, from 0 to the maximum unsigned byte value (almost always 255); returns −1 for end of file.
stdio.h contains a definition for the constant "EOF" as −1.

putchar(c): put one character to the standard output.

Here is a program which functions like "cat" with no arguments, just copying its standard input to its standard output:

#include <stdio.h>

int main()
{
    int c;
    while ((c = getchar()) != EOF)
        putchar(c);
    return(0);
}
Since stdio does buffering, it is not inefficient to be processing this character-by-character.

Note that c must be of type int, not char; if it were of type char, then return values of −1 or 255 from getchar() would end up the same. (It is machine-dependent whether these two values both, or neither, compare as equal to EOF (−1); but in neither case would that program which declared c as type char perform perfectly.)

There is an abstract data type "FILE", defined in stdio.h.

The stdio library function "fopen" takes two arguments:

fopen returns an item of type FILE *; NULL for can't open. In the NULL case, it stashes the reason why...
If there's an error, call perror(filename).

N.B.: That is just about the only correct way to report the error. perror(filename). Not perror(anything else); not a generic error message "can't open file", but an appropriate message printed by perror() such as "filename: Permission denied" or "filename: No such file or directory" or "filename: I/O error". Perror() gives you the correct error message automatically.

fprintf: like printf, with an extra FILE* first argument.

One year in CSC 270 we gave an extension for an assignment but gave a bonus to those who handed it in at the original due time. Some code from the grading software went much like this:

if (getsbonus) {
    char buf[100];
    FILE *fp;
    sprintf(buf, "%s/a3/BONUS", logname);
    if ((fp = fopen(buf, "w")) == NULL) {
        perror(buf);
        exit(1);
    }
    fprintf(fp, "10%% bonus for early submission.  Adjusted grade: ________\n");
    fclose(fp);
}

The fclose() is necessary because this is in a loop for all students. Exiting closes all files, but we needed to close this file before exiting or we would run out of available open files in the middle (e.g. after we opened about 29 files without closing them, if the limit is 32).

Strangely, stdio.h contains "#define NULL 0". Stdio.h is an odd place for this definition, but we tend to use it from there.

A null pointer return from fopen() mean that the open failed. This generally means either that the file doesn't exist or that you don't have permission to access it, although it can also mean out-of-memory (there is a malloc() in fopen for the struct which a FILE* is a pointer to), and a few other weirder things.

Otherwise it is a valid FILE* value which you can pass to other stdio functions.

stdio.h defines stdin, stdout, and stderr as items of type FILE*. (If you want to use the integers 0, 1, or 2 as parameters to unix calls such as read(), you just use the integers, not "stdin" and such, which are items of type FILE*.)

Functions which take a FILE* argument (here using the variable "fp" for the examples):

putc(c, fp);
getc(fp)
fprintf(fp, "....
fscanf(fp, "....

getchar() reads stdin; putchar() puts to stdout. So getchar() is the same as getc(stdin), and putchar(c) is the same as putc(c,stdout). (Note the odd parameter order to putc().)

The function fgets() reads a line of input, defined as a sequence of non-\n characters followed by a \n.
Example usage:

char a[20];
char *p;
printf("What is your name?\n");
fgets(a, 20, stdin);
if ((p = strchr(a, '\n')))
    *p = '\0';
printf("Hello, %s, how are you?\n", a);

fgets() returns an EOF indication too. Its return value is rather strange: It returns either its first argument or a null pointer. Thus its return type is char*. We usually end up testing this directly as a boolean, with the implicit comparison to zero; the implicit zero gets converted to a null pointer of type char*. That is to say, if e is a char* expression, "if (e)" executes the 'then' clause if e is not a null pointer, and the 'else' clause if e is a null pointer.

Suppose we want to input a number.

char buf[80];
int glop;

while (1) {
    printf("Enter the glop number: ");
    if (fgets(buf, 80, stdin) == NULL)
        exit(1);
    if (sscanf(buf, "%d", &glop) == 1)
        break;
    printf("That is not a number.\n");
}
and at this point, the variable "glop" contains a number which the user typed in.

sscanf() is like fscanf(), except instead of reading from an I/O channel it reads from a string.

(If we see end-of-file, there's not much we can do. Simply exiting is a common response to eof if there's no data to save or similar. Like you did in assignment one.)

Another cat example, which takes command-line arguments which are file names (however, the real "cat" takes zero file names as an indication that it should read stdin, and this version doesn't do that):

#include <stdio.h>

int main(int argc, char **argv)
{
    int status = 0;
    for (argc--, argv++; argc > 0; argc--, argv++) {
        FILE *fp;
        if ((fp = fopen(*argv, "r")) == NULL) {
            perror(*argv);
            status = 1;
        } else {
            int c;
            while ((c = getc(fp)) != EOF)
                putchar(c);
            fclose(fp);
        }
    }
    return(status);
}

Finally, a version which reads stdin if zero arguments, and also implements the "−" argument meaning to read stdin, can be found in https://www.teach.cs.toronto.edu/~ajr/209/notes/toolsfiles/cat1.c