Unit 6: File Handling

CAP1008 — C Programming 8 min read

File handling in C lets a program store data permanently on disk instead of losing it when execution ends. All I/O is done through a stream, an abstract flow of bytes connecting the program to a device, and access to that stream is managed through a pointer of type FILE.

  • The FILE pointer: declared as FILE *fp;. FILE is a structure (defined in <stdio.h>) holding the buffer, current position, and status flags. The program never manipulates it directly; it passes fp to library functions.
  • The <stdio.h> header: every file function (fopen, fclose, fprintf, fread, etc.) is declared here, so it must be included.
  • Standard predefined streams: stdin (keyboard), stdout (screen), stderr (error output) are opened automatically at program start.
  • Buffering: data is collected in a memory buffer and transferred in blocks, reducing slow physical disk accesses. A buffer is flushed on fclose, on fflush, or when full.
  • End-of-file (EOF): a macro (value -1) returned by input functions when no more data can be read.
  • Sequential vs random access: bytes may be read one after another, or the position pointer may be moved directly with fseek to reach any location.

II. Categories of Files

Files in C are grouped by how their contents are encoded and interpreted.

A. Stream-based classification

C treats every file as a stream of bytes and distinguishes two encoding categories.

  • Text files: store data as human-readable characters using an encoding such as ASCII. Numbers are stored as their digit characters, so the integer 1234 occupies four bytes '1' '2' '3' '4'.
  • Binary files: store data in the exact internal representation used in memory. The integer 1234 occupies the machine's int size (typically 4 bytes) as a raw bit pattern, not as digits.

B. Access-method classification

Files also differ by the order in which records are reached.

  • Sequential-access files: records are read or written in order from the beginning; reaching record 50 means passing over the first 49.
  • Random-access (direct) files: any record can be reached immediately by repositioning the file pointer with fseek, useful for databases where records have fixed size.

C. Standard vs user-defined streams

  • Standard streams: stdin, stdout, stderr, opened by the runtime, need no fopen.
  • User-defined streams: any disk file the program opens explicitly through fopen, associated with a FILE * variable the programmer manages.

III. Opening and Closing Files

Before data can be transferred, a file must be connected to a stream; after use it must be released. These two operations bracket every file operation.

A. Opening files

fopen associates a named file on disk with a FILE pointer and prepares it for a chosen mode of access.

C
FILE *fp;
fp = fopen("data.txt", "r");   /* filename, mode */
if (fp == NULL) {
    printf("Cannot open file\n");
    return 1;
}
  • Return value: fopen returns a valid FILE * on success, or NULL if the file cannot be opened (missing file, no permission). Always test for NULL.
  • Mode string: the second argument selects the operation and whether the file is text or binary.
    • "r": open existing file for reading; fails if it does not exist.
    • "w": create for writing; an existing file is truncated to zero length (data lost).
    • "a": open for appending; writes go to the end, existing data preserved; created if absent.
    • "r+": read and write an existing file.
    • "w+": create for read and write, truncating any existing file.
    • "a+": open for reading and appending.
  • Binary variants: adding b gives "rb", "wb", "ab", "rb+", etc., opening the file in binary mode so no character translation occurs.

B. Closing files

fclose disconnects the stream, flushing any buffered data to disk and freeing the FILE structure.

C
fclose(fp);
  • Why it matters: unwritten buffer contents are saved only on close or flush; skipping fclose risks losing the last block of output.
  • Return value: returns 0 on success, EOF on failure.
  • Resource limit: the operating system caps how many files may be open at once, so each opened file should be closed when finished.
  • fflush: forces the buffer to disk without closing, e.g. fflush(fp);, useful for ensuring data is saved during a long run.

IV. Text and Binary Files

The choice between text and binary mode determines how bytes are encoded on disk and whether the operating system translates them, so it governs both storage size and portability.

A. Text files

Text files hold sequences of characters organised into lines, readable in any editor.

  • Encoding: each value is stored as printable characters; a float 3.14 becomes the four characters '3' '.' '1' '4'.
  • Newline translation: on Windows the internal '\n' is translated to a carriage-return/line-feed pair (\r\n) on writing and back on reading; Unix keeps a single '\n'.
  • Functions used: character and formatted functions such as fgetc, fputc, fgets, fputs, fscanf, fprintf.
  • Trade-off: portable and human-readable, but larger and slower because numbers must be converted between text and internal form.

B. Binary files

Binary files store the raw memory image of data with no character translation.

  • Encoding: an int, float, or whole struct is copied byte-for-byte, so no conversion cost is incurred.
  • No translation: '\n' is written literally; there is no CR/LF substitution, which is why images, audio, and record files must use binary mode.
  • Functions used: block functions fread and fwrite.
  • Trade-off: compact and fast, but not human-readable and less portable across machines with different byte orders or type sizes.

C. Text versus binary contrasted

  1. Text: editable in Notepad, self-describing, subject to newline translation, wasteful for numeric data because 12345678 needs 8 bytes rather than 4.
  2. Binary: unreadable in a plain editor, no translation, exact and compact, ideal for storing arrays and structures directly with fwrite.

V. Reading and Writing in Files

C provides three families of I/O functions differing in the unit of transfer: single characters, formatted values, and blocks of bytes.

A. Character I/O

These transfer one character at a time and are the simplest way to copy or scan text.

  • fgetc(fp): reads one character, returns it as an int, or EOF at end of file.
  • fputc(ch, fp): writes one character ch to the stream.
C
int ch;
while ((ch = fgetc(fp)) != EOF)   /* read until end of file */
    putchar(ch);
  • getc / putc: near-identical macro versions of fgetc / fputc.

B. String and formatted I/O

These handle lines and mixed data, mirroring scanf/printf on the console.

  • fgets(str, n, fp): reads up to n-1 characters or one line into str, keeping the '\n'; safe because it limits length.
  • fputs(str, fp): writes the string str without adding a newline.
  • fprintf(fp, format, ...): writes formatted output, e.g. fprintf(fp, "%s %d\n", name, age);.
  • fscanf(fp, format, ...): reads formatted input, e.g. fscanf(fp, "%s %d", name, &age);, returning the count of items matched or EOF.

C. Block (record) I/O

fread and fwrite transfer whole blocks and are the natural pair for binary files and structures.

C
fwrite(ptr, size, count, fp);   /* write */
fread(ptr,  size, count, fp);   /* read  */
  • ptr: address of the data in memory.
  • size: size in bytes of one element, usually sizeof(type).
  • count: number of elements to transfer.
  • fp: the stream, opened in binary mode.
  • Return value: the number of complete elements actually transferred; a value below count on reading signals end of file or error.

Worked example — writing then reading a structure:

C
struct Student { char name[20]; int roll; };
struct Student s = {"Asha", 7};

FILE *fp = fopen("stud.dat", "wb");
fwrite(&s, sizeof(s), 1, fp);   /* one record written */
fclose(fp);

fp = fopen("stud.dat", "rb");
fread(&s, sizeof(s), 1, fp);    /* record read back intact */
fclose(fp);

D. Random access with the file pointer

Position functions let reading and writing start anywhere, turning a sequential file into a random-access one.

  • fseek(fp, offset, origin): moves the position offset bytes from origin, where origin is SEEK_SET (start), SEEK_CUR (current), or SEEK_END (end).
  • ftell(fp): returns the current position as a long, useful for finding file size.
  • rewind(fp): resets the position to the beginning, equivalent to fseek(fp, 0, SEEK_SET).
  • Application: to read record n of fixed size, call fseek(fp, n * sizeof(rec), SEEK_SET) then fread, avoiding a scan through earlier records.

E. Detecting end and errors

Reliable I/O checks why a read stopped rather than assuming success.

  • feof(fp): non-zero after an attempt to read past end of file.
  • ferror(fp): non-zero if a read/write error occurred on the stream.
  • EOF return: functions like fgetc and fscanf signal termination directly, so their return value should always be tested inside the loop condition.