C File I/O
C - File I/O
Section titled “C - File I/O”Beyond interacting with standard input (keyboard) and standard output (screen), C programs often need to read data from or write data to files for persistent storage. This chapter covers the standard C library functions for file input/output (I/O).
In C, a file is typically treated as a sequence of bytes, commonly referred to as a stream. The standard I/O library (<stdio.h>) provides functions to manage these streams, whether they represent text files or binary files.
The FILE Structure
Section titled “The FILE Structure”All standard I/O operations work with a pointer to a FILE structure. This structure, defined in <stdio.h>, holds internal information needed to manage the file stream, such as the current position, buffer details, error indicators, and end-of-file status. You typically don’t interact with the members of the FILE structure directly; instead, you pass a FILE * pointer to the various I/O functions.
Opening Files with fopen()
Section titled “Opening Files with fopen()”To work with a file, you first need to open it using the fopen() function. It attempts to associate a stream with the specified file.
FILE *fopen(const char * restrict filename, const char * restrict mode);filename: A string containing the name (and potentially path) of the file to open.
mode: A string specifying how you intend to use the file (read, write, append, etc.). Common modes include:
| Mode | Description (Text Files) |
|---|---|
"r" | Open an existing text file for reading. File must exist. |
"w" | Open a text file for writing. If the file exists, its contents are discarded (truncated). If it doesn’t exist, a new file is created. |
"a" | Open a text file for appending (writing at the end). If the file doesn’t exist, a new file is created. |
"r+" | Open an existing text file for both reading and writing. File must exist. |
"w+" | Open a text file for both reading and writing. Truncates existing file or creates a new one. |
"a+" | Open a text file for reading and appending. Creates file if it doesn’t exist. Initial reading position is the beginning, but writing always appends to the end. |
For binary files, append b to the mode string (e.g., "rb", "wb", "ab", "rb+", "wb+", "ab+"). On Unix-like systems, b often has little effect, but on systems like Windows, it’s crucial for correctly handling binary data (prevents newline character translation).
Return Value: If successful, fopen() returns a non-NULL FILE * pointer associated with the file. If an error occurs (e.g., file not found for "r" mode, permission denied), it returns NULL and sets errno.
Crucial: Always check if the returned pointer is NULL after calling fopen().
Closing Files with fclose()
Section titled “Closing Files with fclose()”When you are finished with a file, you must close it using fclose() to ensure all buffered data is written to disk (flushed) and system resources associated with the file are released.
int fclose(FILE *stream);stream: The FILE * pointer obtained from fopen().
Return Value: Returns 0 on success. Returns EOF (a constant defined in <stdio.h>, usually -1) if an error occurs (e.g., error during final buffer flush). It’s good practice to check the return value of fclose(), especially for files opened for writing.
Writing to Text Files
Section titled “Writing to Text Files”Several functions allow writing to text files:
int fputc(int c, FILE *stream);: Writes a single charactercto the stream. Returns the character written on success, orEOFon error.int fputs(const char * restrict s, FILE * restrict stream);: Writes the null-terminated stringsto the stream (excluding the null terminator). Returns a non-negative value on success, orEOFon error.int fprintf(FILE * restrict stream, const char * restrict format, ...);: Writes formatted output to the stream, similar toprintfbut targeting a file. Returns the number of characters written on success, or a negative value on error.
Example: Writing to a file
#include <stdio.h>#include <stdlib.h> // For EXIT_SUCCESS, EXIT_FAILURE#include <errno.h> // For errno#include <string.h> // For strerror
int main(void) { FILE *fp = NULL; // Initialize pointer to NULL const char *filename = "output.txt"; // Use a relative path
// Open file in write mode ("w+" allows writing and reading) fp = fopen(filename, "w+"); if (fp == NULL) { perror("Error opening file"); return EXIT_FAILURE; }
// Write using fprintf int written = fprintf(fp, "Testing fprintf: line %d\n", 1); if (written < 0) { perror("Error writing with fprintf"); fclose(fp); // Attempt to close even on error return EXIT_FAILURE; }
// Write using fputs if (fputs("Testing fputs: line 2\n", fp) == EOF) { perror("Error writing with fputs"); fclose(fp); return EXIT_FAILURE; }
// Write using fputc if (fputc('3', fp) == EOF) { perror("Error writing with fputc"); fclose(fp); return EXIT_FAILURE; } fputc('\n', fp); // Don't forget newline if needed
printf("Successfully wrote to %s\n", filename);
// Close the file and check for errors if (fclose(fp) == EOF) { perror("Error closing file"); return EXIT_FAILURE; }
return EXIT_SUCCESS;}After running, output.txt will contain:
Testing fprintf: line 1Testing fputs: line 23Reading from Text Files
Section titled “Reading from Text Files”Functions for reading from text files include:
int fgetc(FILE *stream);: Reads the next character from the stream. Returns the character read (as anint) on success, orEOFon end-of-file or error. Usefeof()andferror()to distinguish between EOF and error.char *fgets(char * restrict s, int n, FILE * restrict stream);: Reads at mostn-1characters from the stream into the buffers. Reading stops after a newline (\n) character, at end-of-file, or aftern-1characters. If a newline is read, it is stored in the buffer. A null terminator (\0) is always appended. Returnsson success, orNULLon end-of-file (with no characters read) or error.fgetsis generally safer thanfscanffor reading strings as it prevents buffer overflows.int fscanf(FILE * restrict stream, const char * restrict format, ...);: Reads formatted input from the stream, similar toscanf. Returns the number of input items successfully matched and assigned, orEOFon input failure before any conversion. Warning: Usingfscanfwith%swithout a width limit is dangerous and can lead to buffer overflows if the input is longer than the buffer. Use%<width>s(e.g.,%254s) or preferably usefgets.
Example: Reading from the previously created file
#include <stdio.h>#include <stdlib.h> // For EXIT_SUCCESS, EXIT_FAILURE
#define BUFFER_SIZE 256
int main(void) { FILE *fp = NULL; char buffer[BUFFER_SIZE]; const char *filename = "output.txt";
fp = fopen(filename, "r"); if (fp == NULL) { perror("Error opening file for reading"); return EXIT_FAILURE; }
printf("Reading content using fgets:\n---\n"); // Read line by line using fgets (safer) while (fgets(buffer, BUFFER_SIZE, fp) != NULL) { // Process the line (here, just print it) printf("%s", buffer); // fgets includes the newline, so no extra \n needed } printf("---\n");
// Check if loop ended due to error or EOF if (ferror(fp)) { perror("Error reading file"); } else if (feof(fp)) { printf("End of file reached.\n"); } else { printf("fgets returned NULL without EOF or error set? Unexpected.\n"); }
if (fclose(fp) == EOF) { perror("Error closing file"); return EXIT_FAILURE; }
return EXIT_SUCCESS;}When compiled and executed, this reads output.txt and produces:
Reading content using fgets:---Testing fprintf: line 1Testing fputs: line 23---End of file reached.Binary I/O Functions (fread and fwrite)
Section titled “Binary I/O Functions (fread and fwrite)”For reading and writing binary data (like raw bytes, arrays, or structures), use fread and fwrite. These functions work with blocks of data of a specified size.
size_t fread(void * restrict ptr, size_t size, size_t count, FILE * restrict stream);size_t fwrite(const void * restrict ptr, size_t size, size_t count, FILE * restrict stream);ptr: Pointer to the buffer to read data into (fread) or write data from (fwrite).size: Size (in bytes) of each element to be read/written.count: Number of elements to read/write.stream: TheFILE *pointer.- Return Value: Both functions return the number of elements successfully read or written. This value may be less than
countif an error occurs or the end of the file is reached (fread). Usefeof()andferror()to determine the cause.
Example: Writing and reading a simple structure
#include <stdio.h>#include <stdlib.h>
// Define a simple structuretypedef struct { int id; double value;} Record;
int main(void) { FILE *fp = NULL; const char *filename = "records.bin"; Record rec_write = {101, 3.14159}; Record rec_read; size_t items_written, items_read;
// --- Write the structure to a binary file --- fp = fopen(filename, "wb"); // Note: "wb" mode if (!fp) { perror("Cannot open for writing"); return EXIT_FAILURE; }
items_written = fwrite(&rec_write, sizeof(Record), 1, fp); if (items_written != 1) { fprintf(stderr, "Error writing record (wrote %zu elements).\n", items_written); fclose(fp); return EXIT_FAILURE; } printf("Wrote record: ID=%d, Value=%.5f\n", rec_write.id, rec_write.value); if (fclose(fp) == EOF) { perror("Error closing after write"); return EXIT_FAILURE; }
// --- Read the structure back from the binary file --- fp = fopen(filename, "rb"); // Note: "rb" mode if (!fp) { perror("Cannot open for reading"); return EXIT_FAILURE; }
items_read = fread(&rec_read, sizeof(Record), 1, fp); if (items_read != 1) { if (feof(fp)) { fprintf(stderr, "Error reading record: Unexpected end of file.\n"); } else if (ferror(fp)) { perror("Error reading record"); } fclose(fp); return EXIT_FAILURE; } printf("Read record: ID=%d, Value=%.5f\n", rec_read.id, rec_read.value); if (fclose(fp) == EOF) { perror("Error closing after read"); return EXIT_FAILURE; }
return EXIT_SUCCESS;}Output:
Wrote record: ID=101, Value=3.14159Read record: ID=101, Value=3.14159