Week 6a
File I/O and Command-Line Arguments¶
COP 3223C — Lecture 1 of 2¶
Reading & Writing Files in C¶
Why File I/O?¶
- Programs that only use
stdin/stdoutlose data when they exit - Files let us persist data across runs
- Real-world programs read config files, logs, data sets
- C gives us direct, explicit control over every step
The File I/O Mental Model¶
Your Program
|
fopen() <- open a channel
|
FILE *fp <- handle to the file
|
fprintf() <- write through it
fscanf() <- read through it
|
fclose() <- close the channel
Tip
Think of FILE * like a TV remote —
it controls the device, it’s not the device.
FILE * — The File Handle¶
#include <stdio.h>
int main(void) {
/* Declare a file pointer */
FILE *fp;
/* fp is just a pointer —
it doesn't open anything yet */
fp = NULL;
return 0;
}
FILEis a struct defined in<stdio.h>- We never inspect its internals directly
- Always initialize to
NULLuntil opened
fopen() — Opening a File¶
| Mode | Meaning |
|---|---|
"r" |
Read (file must exist) |
"w" |
Write (creates/truncates) |
"a" |
Append (creates if needed) |
"r+" |
Read + write |
"w+" |
Read + write (truncates) |
"b" |
Binary (append to any mode) |
fopen() in Practice¶
#include <stdio.h>
int main(void) {
FILE *fp;
/* Open for reading */
fp = fopen("data.txt", "r");
/* ALWAYS check the return value */
if (fp == NULL) {
perror("fopen failed");
return 1;
}
/* ... use the file ... */
fclose(fp);
return 0;
}
Why Check for NULL?¶
fopen returns NULL when:
- The file does not exist (mode
"r") - You lack permission to open it
- The path is invalid
- The OS has no more file handles available
Never skip the NULL check — dereferencing a NULL FILE * is undefined behavior.
perror() and errno¶
#include <stdio.h>
#include <errno.h>
fp = fopen("missing.txt", "r");
if (fp == NULL) {
/* Prints: "fopen: No such file
or directory" */
perror("fopen");
/* errno holds the numeric code */
printf("errno = %d\n", errno);
return 1;
}
perror() automatically appends the OS error string to your message.
fclose() — Closing a File¶
- Flushes any buffered output to disk
- Releases the OS file handle
- Returns
0on success,EOFon error - Always close every file you open
Writing: fprintf()¶
/* fprintf works like printf,
but targets a FILE * */
int fprintf(
FILE *fp,
const char *fmt,
...
);
FILE *fp = fopen("out.txt", "w");
if (fp == NULL) { /* handle error */ }
fprintf(fp, "Name: %s\n", "Alice");
fprintf(fp, "Score: %d\n", 95);
fclose(fp);
Writing: fputs() and fputc()¶
/* Write a whole string */
fputs("Hello, file!\n", fp);
/* Write a single character */
fputc('A', fp);
fputc('\n', fp);
fputsdoes not add a newline automaticallyfputcis useful for character-by-character output- Both return
EOFon error
Reading: fscanf()¶
char name[64];
int score;
/* Returns number of items matched */
int n = fscanf(fp, "%63s %d",
name, &score);
if (n != 2) {
/* Short read or error */
}
Reading: fgets()¶
char line[128];
/* Reads up to 127 chars + '\0' */
while (fgets(line, sizeof(line),
fp) != NULL) {
printf("Line: %s", line);
}
- Safer than
fscanffor line-oriented data - Includes the newline in the buffer
- Returns
NULLat EOF or on error
fgets() vs fscanf() — When to Use Each¶
fgets |
fscanf |
|
|---|---|---|
| Unit | One line | One token/field |
| Buffer overflow | Safe (size param) | Risky without width |
| Newline | Included | Skipped |
| Best for | Log files, text lines | Structured data |
Rule of thumb: Use fgets + sscanf for structured line-oriented files.
Combining fgets + sscanf¶
char line[256];
char name[64];
int score;
while (fgets(line, sizeof(line),
fp) != NULL) {
/* Parse the line in memory */
if (sscanf(line, "%63s %d",
name, &score) == 2) {
printf("%s -> %d\n",
name, score);
}
}
Two-pass approach: read safely, then parse safely.
Reading: fgetc() and EOF¶
- Return type is
int— must holdEOF(-1) and all 256 byte values - Assigning to
charbefore checkingEOFis a classic bug
Detecting End-of-File¶
/* After a read returns EOF/NULL */
if (feof(fp)) {
printf("Reached end of file\n");
}
/* Check for I/O error */
if (ferror(fp)) {
perror("Read error");
clearerr(fp);
}
feof()— true only after a failed read- Do not use
feof()as a loop condition (common mistake!)
The feof() Loop Bug¶
/* WRONG — reads last item twice */
while (!feof(fp)) {
fscanf(fp, "%d", &n);
printf("%d\n", n);
}
/* CORRECT — check the read itself */
while (fscanf(fp, "%d", &n) == 1) {
printf("%d\n", n);
}
feof only becomes true after a read has already failed. The loop body runs one extra time.
File Position: fseek() and ftell()¶
/* Move to byte offset from origin */
int fseek(
FILE *fp,
long offset,
int origin
);
/* Return current position */
long ftell(FILE *fp);
| Origin | Meaning |
|---|---|
SEEK_SET |
From start of file |
SEEK_CUR |
From current position |
SEEK_END |
From end of file |
fseek() Example¶
/* Jump to byte 10 from start */
fseek(fp, 10L, SEEK_SET);
/* Save position */
long pos = ftell(fp);
/* Go back to start */
fseek(fp, 0L, SEEK_SET);
/* Jump to end */
fseek(fp, 0L, SEEK_END);
/* File size in bytes */
long size = ftell(fp);
rewind()¶
Useful when you want to read a file a second time from the beginning.
Binary Files: fread() and fwrite()¶
size_t fread(
void *buf,
size_t size, /* bytes per item */
size_t count, /* number of items */
FILE *fp
);
size_t fwrite(
const void *buf,
size_t size,
size_t count,
FILE *fp
);
Binary File Example¶
typedef struct {
char name[32];
int score;
} Record;
Record r = {"Alice", 95};
/* Open in binary write mode */
FILE *fp = fopen("data.bin", "wb");
if (!fp) { /* handle error */ }
fwrite(&r, sizeof(Record), 1, fp);
fclose(fp);
Reading Binary Data Back¶
Record r2;
FILE *fp = fopen("data.bin", "rb");
if (!fp) { /* handle error */ }
size_t n = fread(
&r2, sizeof(Record), 1, fp
);
if (n == 1) {
printf("%s: %d\n",
r2.name, r2.score);
}
fclose(fp);
Text vs Binary Mode¶
Text "r"/"w" |
Binary "rb"/"wb" |
|
|---|---|---|
| Newlines | Translated (\r\n ↔ \n) |
Exact bytes |
| EOF | Ctrl+Z on Windows |
Byte value |
| Use for | Human-readable files | Structs, images, data |
On Linux, text and binary modes are identical — but always use "b" for portability.
Temporary Files¶
#include <stdio.h>
/* Creates a unique temp file,
auto-deleted on fclose/exit */
FILE *tmp = tmpfile();
if (tmp == NULL) {
perror("tmpfile");
}
fprintf(tmp, "scratch data\n");
/* No fclose needed — auto-deleted */
fclose(tmp);
Complete Read-and-Echo Example¶
#include <stdio.h>
int main(void) {
FILE *fp;
char line[256];
fp = fopen("input.txt", "r");
if (fp == NULL) {
perror("fopen");
return 1;
}
while (fgets(line,
sizeof(line),
fp) != NULL) {
fputs(line, stdout);
}
fclose(fp);
return 0;
}
Complete Write Example¶
#include <stdio.h>
int main(void) {
FILE *fp;
int i;
fp = fopen("numbers.txt", "w");
if (fp == NULL) {
perror("fopen");
return 1;
}
for (i = 1; i <= 10; i++) {
fprintf(fp, "%d\n", i);
}
fclose(fp);
printf("Wrote numbers.txt\n");
return 0;
}
Copy-File Example¶
#include <stdio.h>
int main(void) {
FILE *src, *dst;
int c;
src = fopen("in.txt", "r");
dst = fopen("out.txt", "w");
if (!src || !dst) {
perror("fopen");
return 1;
}
while ((c = fgetc(src)) != EOF)
fputc(c, dst);
fclose(src);
fclose(dst);
return 0;
}
Common File I/O Mistakes¶
- Not checking
fopenreturn value - Using
charinstead ofintforfgetc - Using
feof()as a loop condition - Forgetting
fclose()— data may not be flushed - Opening in wrong mode —
"w"destroys existing content - Buffer overflow with
fscanf("%s")— always use width specifier
File I/O Checklist¶
□ #include <stdio.h>
□ Declare FILE *
□ Call fopen() with correct mode
□ Check for NULL return
□ Read/write using f* functions
□ Check read return values
□ Call fclose() when done
□ Set pointer to NULL after close
Lecture 1 Summary¶
| Function | Purpose |
|---|---|
fopen |
Open a file, get FILE * |
fclose |
Close and flush |
fprintf / fputs / fputc |
Write text |
fscanf / fgets / fgetc |
Read text |
fread / fwrite |
Binary I/O |
fseek / ftell |
Seek/position |
feof / ferror |
Status checks |
Next lecture: Command-line arguments + putting it all together
Last update :
June 18, 2026
Created : June 18, 2026
Created : June 18, 2026