CS 240 Lecture 4: More on File I/O

CS 240 Lecture 4: More on File I/O - Detailed Study Notes

Overview and Complete Coverage Check

  • Objective: This lecture provides an exhaustive look into File I/O in the C programming language, focusing on safety, advanced parsing, and error detection.
  • Review of scanf(): Functions as the inverse of printf(). It reads formatted input and returns the number of successful conversions.
  • File I/O Hazards: A "bad" example of File I/O involves declaring a FILE *, calling fopen(), fprintf(), and fclose() without performing a NULL check. This is dangerous and can lead to segfaults.
  • Proper File I/O Sequence:     - Declare and initialize a file pointer to NULL (e.g., FILE *file_ptr = 0).     - Call fopen().     - Check if the pointer is NULL. If true, output an error to stderr and return 1 (or exit).     - Perform file operations (e.g., fprintf()).     - Call fclose().     - Reset the pointer to NULL to avoid dangling pointers.
  • New Functions Introduced:     - access()     - feof()     - ferror()     - clearerr()

The access() Function: Pre-emptive File Verification

  • Purpose: Allows a program to check if a file can be accessed for specific operations (read, write, or existence) BEFORE attempting to open it with fopen(). This acts as a preventative safety check.
  • Signature: int access(char *file_name, int mode)
  • Requirement: Must include the header <unistd.h>.
  • Return Values:     - 0: Success (the file is accessible according to the requested mode).     - Non-zero: Failure (the file is either not found or permissions are denied).
  • Measurement Modes (Constants):     - R_OK: Checks specifically for read permission.     - W_OK: Checks specifically for write permission.     - F_OK: Checks for the existence of the file only (does not check permissions).
  • Critical Distinction: The access() mode constants (R_OK, W_OK, F_OK) are integers and are entirely different from the string modes used in fopen() (e.g., "r", "w", "a"). Mixing these up (e.g., passing 'r' or "r" to access()) is a common trap.

Command-Line Arguments and Main Return Codes

  • Usage with main(): The lecture demonstrates using argc and argv to process filenames passed from the terminal.
  • Definitions:     - argc: The "argument count." It represents the number of command-line arguments, including the program's itself. If a user runs ./prog file.txt, argc is 22.     - argv[]: An array of strings representing the arguments. argv[0] is always the program name; argv[1] is the first user-provided argument.
  • Shell Return Codes:     - return 0 from main(): Signals to the Operating System that the program completed successfully.     - return 1 (or any non-zero value): Signals to the OS that an error occurred (e.g., improper usage or missing file).

Detecting End of File (EOF) with feof()

  • Signature: int feof(FILE *file_pointer)
  • Purpose: Determines if the End Of File was reached by the previous read operation.
  • Return Value: Returns non-zero if EOF has been triggered; returns 00 otherwise.
  • Behavioral Nature: feof() is reactive, not predictive. It only flags EOF after a read operation has failed to find more data.
  • The Exam Trap: Checking feof() before any read leads to errors. If a loop checks feof() as the condition, and performs the read inside, it may process the last line of the file twice because feof() remains 00 until a read actually attempts to cross the EOF boundary and fails.
  • The Priming Read Pattern: To use feof() correctly, one must typically perform a "priming read" before the loop, then check feof(), then perform subsequent reads at the bottom of the loop body.

The Superior EOF Detection Method: fscanf() Return Values

  • Professor's Recommendation: For HW2, students should use the return value of fscanf() as the primary method to detect EOF. It is cleaner and more robust than relying on feof().
  • Value Interpretation:     - NN (Positive integer): Indicates that NN variables were successfully scanned and assigned.     - 00: Indicates a format mismatch or a blank line at the end of the file (no items scanned).     - 1-1: Indicates that the EOF was reached.
  • Implementation Logic:     - Check if the return value (status) is less than the expected count of variables.     - A safe rule for stopping is: if (status == 0 || status == -1) break;.
  • Lecture Quiz Example: fscanf(fp, "%s - %d", some_buf, &some_int) on input string "Squirrel - 7.5".     - Result: fscanf() returns 11.     - Reason: %s successfully captures "Squirrel". The literal string " - " matches. However, %d fails to read 7.5 (which is a float, not an integer). Since only one variable (some_buf) was successfully assigned, the return value is 11.

Advanced File Error Handling: ferror() and clearerr()

  • ferror():     - Signature: int ferror(FILE *file_pointer)     - Function: Checks for general error conditions on a file stream that are not necessarily EOF (e.g., disk full, hardware failure, I/O errors).     - Usage: Call ferror() after every write or read operation to verify success.
  • clearerr():     - Signature: void clearerr(FILE *file_pointer)     - Function: Clears the internal error flag and EOF flag for the given file pointer.     - Utility: Necessary if you intend to retry an operation after resolving an error that was previously flagged by ferror().
  • Example Case Study: A do-while loop writing "Hello, world." to a file until ferror() returns non-zero, indicating the disk is likely full.

Advanced Parsing with the %[] Bracket Specifier

  • Purpose: Matches a set of characters (a "scan set") instead of just a standard type or whitespace-delimited string.
  • Common Patterns:     - %[0-9]: Matches only numeric digits.     - %[A-Z]: Matches only uppercase letters.     - %[0-9A-z]: Matches alphanumeric characters.     - %[^ ]: Matches everything up to a newline (effectively reading a whole line).     - %[^ ] : Reads everything until the newline and then "consumes" (removes) the newline character from the buffer.
  • Inversion via Caret (^): Placing ^ at the start of the set inverts it. %[^0-9] matches anything that is NOT a digit.
  • Special Character Inclusion:     - To include a literal ] in the set: It must be the first character after the opening [. Example: %[]abc].     - To include a literal - (hyphen) in the set: It must be the last character before the closing ]. Example: %[0-9-].

Buffer Overflow: Security Implications and Field Widths

  • The Danger: When using %s or %[] without a limit, scanf() will read characters until it hits a delimiter, regardless of the buffer size. This leads to Buffer Overflows.
  • Mechanism: C does not perform bounds checking. Writing past the allocated memory of an array (e.g., char buf[8]) will corrupt adjacent variables on the stack.
  • Lecture Demo:     - Memory layout: [z: 4 bytes] [buf: 8 bytes] [y: 4 bytes].     - Inputting "toolongstring!" (14 characters) into a 88-byte buffer writes into the space occupied by variableszzandyy`, overwriting their values and causing undefined behavior.
  • The Fix: Always specify a field width that is exactly N1N-1, where NN is the buffer size.     - For char buf[8], use %7s or %7[^ ]. This ensures 77 characters plus the null terminator (\0) fit perfectly within the allocated 88 bytes.

Miscellaneous Preprocessor and Type Details

  • #include Syntax:     - #include "file.h": Searches the current directory first (user-defined headers).     - #include <file.h>: Searches system directories (e.g., /usr/include/).
  • Preprocessor Action: The preprocessor merges all included file content into a single stream for the compiler.
  • The bool Type: Use #include <stdbool.h> to use the bool type with true and false constants.
  • Upcoming Topics Preview: ftell(), fseek(), fgets(), fputs(), and assert().

Study Guide: Visualizations and Summary Table

EOF Detection Strategies
MethodMechanismRecommendation
feof()Reactive: checks after previous read.Moderate; requires "priming read" before loop.
fscanf() ReturnChecks item count; 00 or 1-1 indicates stop.Best Practice (Required for HW2).
ferror()Checks for system/hardware errors (disk full).Best for write-heavy applications.
Character Set Scan Rules
  • Match Digits: %[0-9]
  • Exclude Letters: %[^A-Za-z]
  • Include Right Bracket: %[]...] (must be first)
  • Include Hyphen: %[...-] (must be last)

Active Recall and Practice Problems

Problem: Loop Logic Errors

Identify errors in: char buf[50]; FILE *fp = fopen("data.txt", "r"); while (feof(fp) == 0) { fscanf(fp, "%s", buf); printf("%s\n", buf); }

  • Answer 1: Missing NULL check for fp after fopen().
  • Answer 2: feof() is checked before any read occurs. It will enter the loop even if the file is empty.
  • Answer 3: scanf("%s", buf) lacks a field width (should be %49s), risking buffer overflow.
  • Answer 4: The logic results in the last line being printed twice.
  • Answer 5: No fclose() at the end of the script.
Problem: access() Confusion

Why does if (access("file.txt", 'r') == 0) fail even if the file is readable?

  • Answer: The character 'r' (ASCII 114114) is not the correct constant. You must use R_OK, which is the defined integer constant for the access() function.