CoreTechED

Loading

Learn to delete files in C using the remove() function. Master error handling, file existence checks, and practical file management examples.

C Program to Delete a File: Complete Guide

C Program to Delete a File: A Comprehensive Tutorial

C language allows the manipulation of files present in the accessible storage directory, allowing programmers to create, read, write, and delete files. Deleting a file is a common operation in file management systems, cleanup utilities, and temporary file handling. This tutorial provides an in-depth exploration of deleting files in C using the remove() function, including error handling, practical applications, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is File Deletion?

File deletion is the process of removing a file from the storage system. In C, this is accomplished using the remove() function from the standard library (<stdio.h>).

Why File Deletion Matters

  • Cleanup: Remove temporary files after use.
  • File management: Delete outdated or unwanted files.
  • Data persistence: Manage storage space efficiently.
  • Log rotation: Remove old log files.
  • User operations: Delete files based on user commands.

The remove() Function

The remove() function deletes a file specified by its filename. It returns 0 on success and a non-zero value on failure.

Key characteristics:

  • Declared in <stdio.h>.
  • Takes a filename (string) as argument.
  • Returns 0 on success, non-zero on failure.
  • Can delete both text and binary files.
  • Works on files in accessible storage directories.

Alternative: unlink() (POSIX)

On POSIX systems (Linux, macOS), the unlink() function from <unistd.h> also deletes files. However, remove() is more portable and is the standard C method.


Syntax & Core Mechanics

Basic File Deletion

Syntax:

c

int remove(const char *filename);

Parameters:

  • filename: The name (path) of the file to delete.

Return value:

  • 0: File successfully deleted.
  • Non-zero: Error occurred (file not found, permission denied, etc.).

Example:

c

#include <stdio.h> int main(void) { const char *file = "file.txt"; if (remove(file) == 0) { printf("File deleted successfully.\n"); } else { printf("Error: Unable to delete the file.\n"); } return 0; }

Expected output (if file exists):

text

File deleted successfully.

Expected output (if file does not exist):

text

Error: Unable to delete the file.

Using perror for Detailed Error Messages

c

#include <stdio.h> #include <stdlib.h> int main(void) { const char *file = "file.txt"; if (remove(file) == 0) { printf("File deleted successfully.\n"); } else { perror("Error deleting file"); return EXIT_FAILURE; } return EXIT_SUCCESS; }

Expected output (if file not found):

text

Error deleting file: No such file or directory

Deleting with Path

c

#include <stdio.h> int main(void) { const char *filePath = "/tmp/tempfile.txt"; if (remove(filePath) == 0) { printf("File '%s' deleted.\n", filePath); } else { perror("Error deleting file"); } return 0; }

Checking File Existence Before Deletion

c

#include <stdio.h> int fileExists(const char *filename) { FILE *fp = fopen(filename, "r"); if (fp != NULL) { fclose(fp); return 1; } return 0; } int main(void) { const char *file = "file.txt"; if (fileExists(file)) { if (remove(file) == 0) { printf("File deleted successfully.\n"); } else { perror("Error deleting file"); } } else { printf("File '%s' does not exist.\n", file); } return 0; }

Full Practical Code Example with Explanation

This program demonstrates file deletion in a realistic scenario: a file management utility with create, check, and delete operations.

Program: File Management Utility

c

/** * file_deletion_manager.c * Demonstrates file deletion using remove() with robust error handling. * Includes file creation, existence check, and deletion operations. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 200 #define MAX_FILENAME_LEN 100 /* Function prototypes */ int createFile(const char *filename, const char *content); int fileExists(const char *filename); int deleteFile(const char *filename); void demonstrateBasicDeletion(void); void demonstrateErrorHandling(void); void demonstrateFileManager(void); void listFiles(void); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== File Deletion Manager ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Demonstrate Basic Deletion\n"); printf("2. Demonstrate Error Handling\n"); printf("3. File Manager (Create/Check/Delete)\n"); printf("4. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: demonstrateBasicDeletion(); break; case 2: demonstrateErrorHandling(); break; case 3: demonstrateFileManager(); break; case 4: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Creates a file with given content */ int createFile(const char *filename, const char *content) { FILE *fp = fopen(filename, "w"); if (fp == NULL) { fprintf(stderr, "Error creating %s: %s\n", filename, strerror(errno)); return -1; } if (content != NULL) { fprintf(fp, "%s", content); } fclose(fp); printf("Created file: %s\n", filename); return 0; } /* Checks if a file exists */ int fileExists(const char *filename) { FILE *fp = fopen(filename, "r"); if (fp != NULL) { fclose(fp); return 1; } return 0; } /* Deletes a file with detailed error reporting */ int deleteFile(const char *filename) { if (remove(filename) == 0) { printf("File '%s' deleted successfully.\n", filename); return 0; } else { fprintf(stderr, "Error deleting '%s': %s\n", filename, strerror(errno)); return -1; } } /* Demonstrates basic file deletion */ void demonstrateBasicDeletion(void) { printf("\n=== Basic File Deletion ===\n"); const char *tempFile = "temp_basic.txt"; /* Create a temporary file */ createFile(tempFile, "This file will be deleted.\n"); /* Check if file exists */ if (fileExists(tempFile)) { printf("File '%s' exists.\n", tempFile); } /* Delete the file */ if (deleteFile(tempFile) == 0) { printf("Deletion successful.\n"); } /* Verify deletion */ if (!fileExists(tempFile)) { printf("File '%s' no longer exists.\n", tempFile); } } /* Demonstrates error handling in file deletion */ void demonstrateErrorHandling(void) { printf("\n=== Error Handling ===\n"); /* Case 1: Delete non-existent file */ const char *nonExistent = "non_existent_file.txt"; printf("Attempting to delete non-existent file:\n"); deleteFile(nonExistent); /* Case 2: Delete with empty filename */ printf("\nAttempting to delete with empty filename:\n"); deleteFile(""); /* Case 3: Delete file with path */ printf("\nDeleting file with path:\n"); const char *pathFile = "temp_path.txt"; createFile(pathFile, "File with path.\n"); deleteFile(pathFile); } /* Demonstrates a file manager with user interaction */ void demonstrateFileManager(void) { printf("\n=== File Manager ===\n"); char filename[MAX_FILENAME_LEN]; char buffer[MAX_LINE_LEN]; int choice; while (1) { printf("\n--- File Manager Menu ---\n"); printf("1. Create File\n"); printf("2. Check File Exists\n"); printf("3. Delete File\n"); printf("4. Return to Main Menu\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return; } choice = atoi(buffer); switch (choice) { case 1: printf("Enter filename: "); fflush(stdout); if (fgets(filename, sizeof(filename), stdin) != NULL) { filename[strcspn(filename, "\n")] = '\0'; createFile(filename, "Sample content.\n"); } break; case 2: printf("Enter filename: "); fflush(stdout); if (fgets(filename, sizeof(filename), stdin) != NULL) { filename[strcspn(filename, "\n")] = '\0'; if (fileExists(filename)) { printf("File '%s' exists.\n", filename); } else { printf("File '%s' does not exist.\n", filename); } } break; case 3: printf("Enter filename: "); fflush(stdout); if (fgets(filename, sizeof(filename), stdin) != NULL) { filename[strcspn(filename, "\n")] = '\0'; deleteFile(filename); } break; case 4: printf("Returning to main menu.\n"); return; default: printf("Invalid choice. Please try again.\n"); break; } } }

Explanation of File Deletion Usage

  1. remove(filename): Core function for file deletion.
  2. fileExists: Checks if file exists before deletion.
  3. deleteFile: Wraps remove with detailed error reporting.
  4. Error handling: Uses strerror(errno) for descriptive errors.
  5. File creation: Creates test files for demonstration.

Expected Output (sample run)

text

=== File Deletion Manager === --- Menu --- 1. Demonstrate Basic Deletion 2. Demonstrate Error Handling 3. File Manager (Create/Check/Delete) 4. Exit Enter choice: 1 === Basic File Deletion === Created file: temp_basic.txt File 'temp_basic.txt' exists. File 'temp_basic.txt' deleted successfully. Deletion successful. File 'temp_basic.txt' no longer exists. --- Menu --- ... Enter choice: 2 === Error Handling === Attempting to delete non-existent file: Error deleting 'non_existent_file.txt': No such file or directory Attempting to delete with empty filename: Error deleting '': No such file or directory Deleting file with path: Created file: temp_path.txt File 'temp_path.txt' deleted successfully. --- Menu --- ... Enter choice: 3 === File Manager === --- File Manager Menu --- 1. Create File 2. Check File Exists 3. Delete File 4. Return to Main Menu Enter choice: 1 Enter filename: myfile.txt Created file: myfile.txt --- File Manager Menu --- ... Enter choice: 2 Enter filename: myfile.txt File 'myfile.txt' exists. --- File Manager Menu --- ... Enter choice: 3 Enter filename: myfile.txt File 'myfile.txt' deleted successfully.

Common Pitfalls, Edge Cases & Memory Considerations

1. Deleting Non-Existent File

remove() returns non-zero if the file doesn’t exist.

c

if (remove("missing.txt") != 0) { perror("Error"); // No such file or directory }

Solution: Check return value and handle gracefully.

2. Permission Issues

Deleting a file without appropriate permissions fails.

c

remove("/root/protected.txt"); // May fail with permission denied

Solution: Check errno and inform user about permission issues.

3. Deleting Open Files

On some systems, deleting an open file may succeed (POSIX) or fail (Windows).

Solution: Close all file handles before attempting deletion.

4. Race Conditions

Another process may delete the file between existence check and deletion.

Solution: Use the return value of remove() as the definitive result.

5. Empty Filename

Calling remove("") fails with an error.

c

remove(""); // Returns non-zero

Solution: Validate filename before deletion.

6. Path Issues

Relative paths are resolved from the current working directory.

c

remove("file.txt"); // Current directory remove("/tmp/file.txt"); // Absolute path remove("subdir/file.txt"); // Relative path

Solution: Ensure the path is correct for the intended file.

7. Symbolic Links

On POSIX systems, remove() on a symlink removes the link, not the target.

Solution: Be aware of symlink behavior on the target platform.

8. Directory vs. File

remove() on POSIX can remove empty directories, but on Windows, it only removes files.

Solution: Use rmdir() for directories (POSIX) or platform-specific functions.

9. Error Code Checking

Using errno directly without checking return value may give stale errors.

c

remove("file.txt"); printf("%s\n", strerror(errno)); // BUG: errno may be stale

Solution: Only check errno after remove() returns non-zero.

10. Memory Considerations

  • remove() does not allocate memory.
  • Filename strings must be valid null-terminated strings.
  • No memory leaks from file deletion itself.
  • Ensure dynamically allocated filename strings are freed after use.

Summary & Best Practices

File deletion in C is straightforward using the remove() function. Proper error handling and validation ensure robust file management.

Key Takeaways

  • remove(filename) deletes a file; returns 0 on success.
  • Non-zero return indicates error (check errno).
  • Use perror or strerror for descriptive errors.
  • Check file existence with fopen before deletion (optional).
  • Close files before deleting them.
  • Ensure appropriate permissions for deletion.

Best Practices

  1. Always check remove() return value.
  2. Use perror or strerror(errno) for error reporting.
  3. Validate filenames (not empty, correct path).
  4. Close files before deleting them.
  5. Check for file existence before deletion (if needed).
  6. Handle permission errors gracefully.
  7. Use absolute paths for clarity when necessary.
  8. Be aware of platform differences (POSIX vs Windows).
  9. Test with edge cases: non-existent file, empty filename, permission denied.
  10. Document file deletion operations in comments.

By mastering file deletion in C, you’ll write robust file management programs that handle cleanup and storage efficiently.

Learn C EOF, getc, and feof: detect end of file, distinguish EOF from read errors, and master robust file reading with examples.

EOF, getc() and feof() in C: Complete Guide

EOF, getc() and feof() in C: A Comprehensive Tutorial

When reading data from files in C, it is important to detect when the end of a file is reached and handle input operations correctly. The EOF constant, along with functions such as getc() and feof(), helps developers perform reliable and error-free file reading. Understanding the distinction between end-of-file and read errors is crucial for robust file handling.

This tutorial provides an in-depth exploration of EOF, getc(), and feof() in C: their syntax, usage, the need for feof(), practical examples, common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is EOF?

EOF (End of File) is a predefined constant macro in C that indicates the end of a file during file input operations. It is defined in the <stdio.h> header file.

Key characteristics:

  • Used by file input functions like getc(), fgetc(), and scanf().
  • Value is implementation-defined, but commonly -1.
  • Not an actual character; it’s a sentinel value.

What is getc()?

getc() is a function that reads a single character from a file stream. It returns the character read (as an int) or EOF on end-of-file or error.

What is feof()?

feof() is a function that checks whether the end-of-file indicator has been set for a given file stream. It returns non-zero if EOF has been reached, and 0 otherwise.

Why feof() is Needed

getc() returns EOF both when:

  1. The end of the file is reached.
  2. A read error occurs.

To distinguish between these two cases, feof() is used to specifically check if the end-of-file condition caused the EOF return.


Syntax & Core Mechanics

The EOF Constant

Definition:

c

#define EOF (-1) // Common implementation

Usage:

c

#include <stdio.h> int main(void) { FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { fprintf(stderr, "File not found.\n"); return 1; } int ch; while ((ch = fgetc(fp)) != EOF) { putchar(ch); } fclose(fp); return 0; }

getc() Function

Syntax:

c

int getc(FILE *stream);

Return value:

  • The character read (as unsigned char cast to int) on success.
  • EOF on end-of-file or error.

Example:

c

#include <stdio.h> int main(void) { FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { fprintf(stderr, "File not found.\n"); return 1; } int ch; while ((ch = getc(fp)) != EOF) { putchar(ch); } fclose(fp); return 0; }

feof() Function

Syntax:

c

int feof(FILE *stream);

Return value:

  • Non-zero if the end-of-file indicator is set.
  • 0 if the end-of-file indicator is not set.

Example:

c

#include <stdio.h> int main(void) { FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { fprintf(stderr, "File not found.\n"); return 1; } while (getc(fp) != EOF); // Read until EOF if (feof(fp)) { printf("End of file reached.\n"); } fclose(fp); return 0; }

Distinguishing EOF from Error

c

#include <stdio.h> int main(void) { FILE *fp = fopen("file.txt", "w"); // Write mode int ch; ch = getc(fp); // Attempt to read from write-only file if (ch == EOF) { if (feof(fp)) { printf("End of file reached.\n"); } else if (ferror(fp)) { printf("Read error occurred.\n"); } else { printf("Unable to read (unknown reason).\n"); } } else { printf("Read character: %c\n", ch); } fclose(fp); return 0; }

getc() vs fgetc()

Both are equivalent, but getc() may be implemented as a macro (faster) while fgetc() is always a function.

c

int ch1 = getc(fp); // May be macro int ch2 = fgetc(fp); // Always function

Full Practical Code Example with Explanation

This program demonstrates EOF, getc(), and feof() in a comprehensive file reading scenario with error handling.

Program: File Reader with Error Detection

c

/** * file_reader_eof.c * Demonstrates EOF, getc, feof, and ferror for robust file reading. * Distinguishes between end-of-file and read errors. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes */ void readFileWithGetc(const char *filename); void readFileWithFgets(const char *filename); void demonstrateEofVsError(const char *filename); void createSampleFile(const char *filename); void countCharacters(const char *filename); void countLines(const char *filename); int main(void) { int choice; char buffer[MAX_LINE_LEN]; char filename[MAX_LINE_LEN] = "sample.txt"; /* Create sample file */ createSampleFile(filename); printf("=== File Reader with EOF Detection ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Read File with getc() and EOF\n"); printf("2. Count Characters\n"); printf("3. Count Lines\n"); printf("4. Demonstrate EOF vs Error\n"); printf("5. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: readFileWithGetc(filename); break; case 2: countCharacters(filename); break; case 3: countLines(filename); break; case 4: demonstrateEofVsError(filename); break; case 5: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Reads file using getc() with proper EOF handling */ void readFileWithGetc(const char *filename) { printf("\n=== Reading with getc() ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } int ch; int count = 0; while ((ch = getc(fp)) != EOF) { putchar(ch); count++; } printf("\n(Read %d characters)\n", count); /* Check why loop ended */ if (feof(fp)) { printf("Reason: End of file reached.\n"); } else if (ferror(fp)) { printf("Reason: Read error occurred.\n"); } fclose(fp); } /* Counts characters in a file */ void countCharacters(const char *filename) { printf("\n=== Character Count ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } int ch; long count = 0; while ((ch = getc(fp)) != EOF) { count++; } if (feof(fp)) { printf("Total characters: %ld\n", count); } else { printf("Read error occurred after %ld characters.\n", count); } fclose(fp); } /* Counts lines in a file */ void countLines(const char *filename) { printf("\n=== Line Count ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } int ch; long lines = 0; while ((ch = getc(fp)) != EOF) { if (ch == '\n') { lines++; } } if (feof(fp)) { printf("Total lines: %ld\n", lines); } fclose(fp); } /* Demonstrates difference between EOF and read error */ void demonstrateEofVsError(const char *filename) { printf("\n=== EOF vs Error Demonstration ===\n"); /* Case 1: Reading from write-only file */ FILE *fp = fopen("temp_write_only.txt", "w"); if (fp == NULL) { fprintf(stderr, "Error creating temp file.\n"); return; } int ch = getc(fp); // Attempt to read from write-only file if (ch == EOF) { if (feof(fp)) { printf("Write-only file: End of file reached.\n"); } else if (ferror(fp)) { printf("Write-only file: Read error occurred (ferror set).\n"); } else { printf("Write-only file: Unable to read (no EOF, no error).\n"); } } fclose(fp); /* Case 2: Reading from valid file */ fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s.\n", filename); return; } while ((ch = getc(fp)) != EOF); // Read until EOF if (feof(fp)) { printf("Valid file: End of file reached (feof set).\n"); } fclose(fp); } /* Creates a sample text file */ void createSampleFile(const char *filename) { FILE *fp = fopen(filename, "w"); if (fp == NULL) { fprintf(stderr, "Error creating %s.\n", filename); return; } fprintf(fp, "Hello, World!\n"); fprintf(fp, "This is a sample file.\n"); fprintf(fp, "Used for demonstrating EOF, getc, and feof.\n"); fclose(fp); printf("Created sample file: %s\n", filename); }

Explanation of EOF, getc, and feof Usage

  1. getc(fp): Reads one character; returns EOF on end or error.
  2. EOF: Sentinel value indicating end-of-file or error.
  3. feof(fp): Checks if end-of-file indicator is set.
  4. ferror(fp): Checks if error indicator is set.
  5. Proper loop: while ((ch = getc(fp)) != EOF) then check feof/ferror after loop.

Expected Output (sample run)

text

=== File Reader with EOF Detection === Created sample file: sample.txt --- Menu --- 1. Read File with getc() and EOF 2. Count Characters 3. Count Lines 4. Demonstrate EOF vs Error 5. Exit Enter choice: 1 === Reading with getc() === Hello, World! This is a sample file. Used for demonstrating EOF, getc, and feof. (Read 74 characters) Reason: End of file reached. --- Menu --- ... Enter choice: 2 === Character Count === Total characters: 74 --- Menu --- ... Enter choice: 3 === Line Count === Total lines: 3 --- Menu --- ... Enter choice: 4 === EOF vs Error Demonstration === Write-only file: Read error occurred (ferror set). Valid file: End of file reached (feof set).

Common Pitfalls, Edge Cases & Memory Considerations

1. Using char Instead of int for getc

char may not represent EOF (which is typically -1).

c

char ch; // BUG: should be int while ((ch = getc(fp)) != EOF) { ... }

Solution: Use int ch; to correctly handle EOF.

2. Not Checking feof and ferror

Assuming the loop ended solely due to EOF may miss read errors.

c

while ((ch = getc(fp)) != EOF) { ... } /* Missing feof/ferror check */

Solution: Check feof(fp) and ferror(fp) after the loop.

3. Using feof as Loop Condition

Using feof as the loop condition is an anti-pattern.

c

while (!feof(fp)) { // BUG: may process last character twice ch = getc(fp); putchar(ch); }

Solution: Use the return value of the read function:

c

while ((ch = getc(fp)) != EOF) { putchar(ch); }

4. feof Does Not Predict Future

feof only returns non-zero after attempting to read past the end of file.

c

/* feof is 0 here even if next read will return EOF */ if (feof(fp)) { ... }

Solution: Call getc first, then check feof.

5. Forgetting to Close Files

Leaving files open wastes resources.

c

FILE *fp = fopen("file.txt", "r"); /* ... reading ... */ /* Missing fclose(fp); */

Solution: Always fclose after use.

6. Mixing Text and Binary Modes

Using text mode for binary files can cause EOF confusion.

Solution: Use correct modes ("rb" for binary, "r" for text).

7. Assuming EOF is Always -1

While commonly -1, EOF value is implementation-defined.

Solution: Always use the EOF macro, not the literal -1.

8. Not Checking fopen Return

Using NULL file pointer causes crashes.

c

FILE *fp = fopen("missing.txt", "r"); getc(fp); // BUG: fp is NULL

Solution: Check if (fp != NULL) after opening.

9. getc on Write-Only File

Reading from a write-only file causes undefined behavior.

c

FILE *fp = fopen("file.txt", "w"); int ch = getc(fp); // BUG: file not opened for reading

Solution: Open file with correct mode for intended operation.

10. Memory Considerations

  • getc reads one byte at a time; use buffered reads for efficiency.
  • Large files processed character by character are slow.
  • Consider using fgets or fread for bulk reading.
  • Always free dynamically allocated memory.

Summary & Best Practices

Understanding EOF, getc(), and feof() is essential for robust file reading in C. Proper handling distinguishes between end-of-file and read errors, ensuring reliable program behavior.

Key Takeaways

  • EOF is a sentinel value (commonly -1) indicating end-of-file or error.
  • getc() reads one character; returns EOF on end or error.
  • feof() checks if end-of-file indicator is set.
  • ferror() checks if error indicator is set.
  • Use int for getc return value.
  • Use return value of read function for loop control, not feof.

Best Practices

  1. Use int ch for getc return value.
  2. Use while ((ch = getc(fp)) != EOF) for loops.
  3. Check feof(fp) and ferror(fp) after the loop.
  4. Never use feof as the loop condition.
  5. Always check fopen return value.
  6. Always close files with fclose.
  7. Use correct file modes ("r", "rb", etc.).
  8. Use the EOF macro, not the literal -1.
  9. For line reading, prefer fgets over character-by-character.
  10. Test edge cases: empty files, missing files, write-only files.

By mastering EOF, getc(), and feof(), you’ll write robust file-reading code that correctly handles end-of-file and error conditions.