C — File I/O
File I/O is how C programs persist data and communicate with the outside world. The standard library provides a buffered, portable interface through FILE streams. This page covers every major I/O function, from character-level reads to binary record databases.
FILE is an opaque type defined by the standard library. It encapsulates the file descriptor, buffering state, error flags, and EOF indicator. You always work withFILE* pointers:
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | FILE *fp = fopen("example.txt", "w"); |
| 5 | if (!fp) { |
| 6 | perror("fopen"); |
| 7 | return 1; |
| 8 | } |
| 9 | |
| 10 | fprintf(fp, "Hello, file!\n"); |
| 11 | fclose(fp); |
| 12 | return 0; |
| 13 | } |
info
fopen(filename, mode) opens a stream. The mode string controls read/write access and positioning:
| Mode | Description | File Must Exist | Position |
|---|---|---|---|
| "r" | Read only | Yes | Beginning |
| "w" | Write only, truncates | No (creates) | Beginning |
| "a" | Append only | No (creates) | End |
| "rb" | Binary read | Yes | Beginning |
| "wb" | Binary write, truncates | No (creates) | Beginning |
| "r+" | Read + write | Yes | Beginning |
| "w+" | Read + write, truncates | No (creates) | Beginning |
| "a+" | Read + append | No (creates) | End (writes always append) |
| 1 | FILE *fp; |
| 2 | |
| 3 | fp = fopen("data.txt", "r"); /* read existing file */ |
| 4 | if (!fp) { /* handle error */ } |
| 5 | |
| 6 | fp = fopen("output.txt", "w"); /* create/overwrite */ |
| 7 | if (!fp) { /* handle error */ } |
| 8 | |
| 9 | fp = fopen("log.txt", "a"); /* append to end */ |
| 10 | if (!fp) { /* handle error */ } |
| 11 | |
| 12 | fp = fopen("image.bin", "rb"); /* binary read */ |
| 13 | if (!fp) { /* handle error */ } |
| 14 | |
| 15 | fp = fopen("data.bin", "wb"); /* binary write */ |
| 16 | if (!fp) { /* handle error */ } |
fclose flushes any buffered output and releases the stream. Failing to close files leaks file descriptors. For program exit, the OS reclaims them, but in long-running programs this is a resource leak:
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | int write_data(const char *path) { |
| 5 | FILE *fp = fopen(path, "w"); |
| 6 | if (!fp) return -1; |
| 7 | |
| 8 | for (int i = 0; i < 1000; i++) { |
| 9 | fprintf(fp, "Line %d\n", i); |
| 10 | } |
| 11 | |
| 12 | if (fclose(fp) != 0) { |
| 13 | perror("fclose"); |
| 14 | return -1; |
| 15 | } |
| 16 | return 0; |
| 17 | } |
| 18 | |
| 19 | int main(void) { |
| 20 | if (write_data("output.txt") != 0) { |
| 21 | fprintf(stderr, "Failed to write data\n"); |
| 22 | return 1; |
| 23 | } |
| 24 | printf("Done.\n"); |
| 25 | return 0; |
| 26 | } |
warning
The errno global (from<errno.h>) is set by failing system calls. Pair it with perroror strerror for diagnostics:
| 1 | #include <stdio.h> |
| 2 | #include <errno.h> |
| 3 | #include <string.h> |
| 4 | |
| 5 | void open_file(const char *path) { |
| 6 | FILE *fp = fopen(path, "r"); |
| 7 | if (!fp) { |
| 8 | fprintf(stderr, "Cannot open '%s': %s\n", |
| 9 | path, strerror(errno)); |
| 10 | return; |
| 11 | } |
| 12 | printf("Opened %s successfully\n", path); |
| 13 | fclose(fp); |
| 14 | } |
| 15 | |
| 16 | int main(void) { |
| 17 | open_file("nonexistent.txt"); |
| 18 | /* prints: Cannot open 'nonexistent.txt': No such file or directory */ |
| 19 | return 0; |
| 20 | } |
These are the low-level building blocks. Every other I/O function is built on top of them. They handle EOF and errors through return values:
| 1 | #include <stdio.h> |
| 2 | #include <ctype.h> |
| 3 | |
| 4 | /* Count characters, words, and lines */ |
| 5 | void count_file(const char *path) { |
| 6 | FILE *fp = fopen(path, "r"); |
| 7 | if (!fp) { perror("fopen"); return; } |
| 8 | |
| 9 | int ch; |
| 10 | long chars = 0, words = 0, lines = 0; |
| 11 | int in_word = 0; |
| 12 | |
| 13 | while ((ch = fgetc(fp)) != EOF) { |
| 14 | chars++; |
| 15 | if (ch == '\n') lines++; |
| 16 | if (isspace(ch)) { |
| 17 | in_word = 0; |
| 18 | } else if (!in_word) { |
| 19 | in_word = 1; |
| 20 | words++; |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | printf("%ld chars, %ld words, %ld lines\n", chars, words, lines); |
| 25 | fclose(fp); |
| 26 | } |
| 27 | |
| 28 | /* Write a string character by character */ |
| 29 | void write_chars(FILE *fp, const char *s) { |
| 30 | while (*s) { |
| 31 | fputc(*s++, fp); |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | int main(void) { |
| 36 | /* Write test file */ |
| 37 | FILE *fp = fopen("test.txt", "w"); |
| 38 | write_chars(fp, "Hello\nWorld\nFoo Bar Baz\n"); |
| 39 | fclose(fp); |
| 40 | |
| 41 | count_file("test.txt"); |
| 42 | return 0; |
| 43 | } |
fgets(buf, n, fp) reads up to n-1 characters or until a newline. It always null-terminates. This is the safest way to read text lines — unlikegets which was removed in C11:
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | #define MAX_LINE 1024 |
| 5 | |
| 6 | void copy_file(const char *src, const char *dst) { |
| 7 | FILE *in = fopen(src, "r"); |
| 8 | FILE *out = fopen(dst, "w"); |
| 9 | if (!in || !out) { perror("fopen"); return; } |
| 10 | |
| 11 | char line[MAX_LINE]; |
| 12 | while (fgets(line, sizeof(line), in) != NULL) { |
| 13 | fputs(line, out); |
| 14 | } |
| 15 | |
| 16 | fclose(in); |
| 17 | fclose(out); |
| 18 | } |
| 19 | |
| 20 | void print_numbered(const char *path) { |
| 21 | FILE *fp = fopen(path, "r"); |
| 22 | if (!fp) { perror("fopen"); return; } |
| 23 | |
| 24 | char line[MAX_LINE]; |
| 25 | int lineno = 1; |
| 26 | while (fgets(line, sizeof(line), fp) != NULL) { |
| 27 | printf("%4d: %s", lineno++, line); |
| 28 | } |
| 29 | |
| 30 | fclose(fp); |
| 31 | } |
| 32 | |
| 33 | int main(void) { |
| 34 | /* Create a test file */ |
| 35 | FILE *fp = fopen("source.txt", "w"); |
| 36 | fprintf(fp, "first line\nsecond line\nthird line\n"); |
| 37 | fclose(fp); |
| 38 | |
| 39 | copy_file("source.txt", "copy.txt"); |
| 40 | print_numbered("source.txt"); |
| 41 | return 0; |
| 42 | } |
fprintf andfscanf are the file-based equivalents of printf andscanf. They support the same format specifiers:
| 1 | #include <stdio.h> |
| 2 | |
| 3 | struct Record { |
| 4 | int id; |
| 5 | char name[64]; |
| 6 | double value; |
| 7 | }; |
| 8 | |
| 9 | int save_records(const char *path, const struct Record *recs, int n) { |
| 10 | FILE *fp = fopen(path, "w"); |
| 11 | if (!fp) return -1; |
| 12 | |
| 13 | fprintf(fp, "%d\n", n); |
| 14 | for (int i = 0; i < n; i++) { |
| 15 | fprintf(fp, "%d,%s,%.2f\n", recs[i].id, recs[i].name, recs[i].value); |
| 16 | } |
| 17 | |
| 18 | fclose(fp); |
| 19 | return 0; |
| 20 | } |
| 21 | |
| 22 | int load_records(const char *path, struct Record *recs, int max) { |
| 23 | FILE *fp = fopen(path, "r"); |
| 24 | if (!fp) return -1; |
| 25 | |
| 26 | int n = 0; |
| 27 | fscanf(fp, "%d\n", &n); |
| 28 | if (n > max) n = max; |
| 29 | |
| 30 | for (int i = 0; i < n; i++) { |
| 31 | fscanf(fp, "%d,%63[^,],%lf\n", |
| 32 | &recs[i].id, recs[i].name, &recs[i].value); |
| 33 | } |
| 34 | |
| 35 | fclose(fp); |
| 36 | return n; |
| 37 | } |
| 38 | |
| 39 | int main(void) { |
| 40 | struct Record data[] = { |
| 41 | { 1, "Alpha", 99.5 }, |
| 42 | { 2, "Beta", 42.0 }, |
| 43 | { 3, "Gamma", 7.25 }, |
| 44 | }; |
| 45 | int n = sizeof(data) / sizeof(data[0]); |
| 46 | |
| 47 | save_records("records.csv", data, n); |
| 48 | |
| 49 | struct Record loaded[10]; |
| 50 | int count = load_records("records.csv", loaded, 10); |
| 51 | |
| 52 | for (int i = 0; i < count; i++) { |
| 53 | printf("#%d %s = %.2f\n", loaded[i].id, loaded[i].name, loaded[i].value); |
| 54 | } |
| 55 | return 0; |
| 56 | } |
For binary data, fread andfwrite transfer raw bytes. They are essential for image files, serialized structures, and any non-text format:
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | typedef struct { |
| 5 | char magic[4]; /* "BIN1" */ |
| 6 | uint32_t version; |
| 7 | uint32_t count; |
| 8 | } FileHeader; |
| 9 | |
| 10 | typedef struct { |
| 11 | uint32_t id; |
| 12 | double values[4]; |
| 13 | char label[16]; |
| 14 | } DataPoint; |
| 15 | |
| 16 | int write_binary(const char *path, const DataPoint *pts, uint32_t n) { |
| 17 | FILE *fp = fopen(path, "wb"); |
| 18 | if (!fp) return -1; |
| 19 | |
| 20 | FileHeader hdr = { .magic = "BIN1", .version = 1, .count = n }; |
| 21 | fwrite(&hdr, sizeof(hdr), 1, fp); |
| 22 | fwrite(pts, sizeof(DataPoint), n, fp); |
| 23 | |
| 24 | fclose(fp); |
| 25 | return 0; |
| 26 | } |
| 27 | |
| 28 | int read_binary(const char *path, DataPoint *pts, uint32_t max) { |
| 29 | FILE *fp = fopen(path, "rb"); |
| 30 | if (!fp) return -1; |
| 31 | |
| 32 | FileHeader hdr; |
| 33 | if (fread(&hdr, sizeof(hdr), 1, fp) != 1) { |
| 34 | fclose(fp); |
| 35 | return -1; |
| 36 | } |
| 37 | |
| 38 | if (memcmp(hdr.magic, "BIN1", 4) != 0 || hdr.version != 1) { |
| 39 | fclose(fp); |
| 40 | return -1; |
| 41 | } |
| 42 | |
| 43 | uint32_t to_read = hdr.count < max ? hdr.count : max; |
| 44 | size_t read = fread(pts, sizeof(DataPoint), to_read, fp); |
| 45 | fclose(fp); |
| 46 | return (int)read; |
| 47 | } |
| 48 | |
| 49 | int main(void) { |
| 50 | DataPoint data[] = { |
| 51 | { .id = 1, .values = {1.0, 2.0, 3.0, 4.0}, .label = "first" }, |
| 52 | { .id = 2, .values = {5.0, 6.0, 7.0, 8.0}, .label = "second" }, |
| 53 | }; |
| 54 | |
| 55 | write_binary("data.bin", data, 2); |
| 56 | |
| 57 | DataPoint loaded[10]; |
| 58 | int n = read_binary("data.bin", loaded, 10); |
| 59 | |
| 60 | for (int i = 0; i < n; i++) { |
| 61 | printf("#%u %s: [%.1f, %.1f, %.1f, %.1f]\n", |
| 62 | loaded[i].id, loaded[i].label, |
| 63 | loaded[i].values[0], loaded[i].values[1], |
| 64 | loaded[i].values[2], loaded[i].values[3]); |
| 65 | } |
| 66 | return 0; |
| 67 | } |
best practice
To read an entire file into memory, seek to the end to get the size, allocate, then read:
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | char *read_file(const char *path, long *out_size) { |
| 5 | FILE *fp = fopen(path, "rb"); |
| 6 | if (!fp) return NULL; |
| 7 | |
| 8 | fseek(fp, 0, SEEK_END); |
| 9 | long size = ftell(fp); |
| 10 | rewind(fp); |
| 11 | |
| 12 | char *buf = malloc(size + 1); |
| 13 | if (!buf) { fclose(fp); return NULL; } |
| 14 | |
| 15 | size_t read = fread(buf, 1, size, fp); |
| 16 | buf[read] = '\0'; |
| 17 | fclose(fp); |
| 18 | |
| 19 | if (out_size) *out_size = (long)read; |
| 20 | return buf; |
| 21 | } |
| 22 | |
| 23 | int main(void) { |
| 24 | long size = 0; |
| 25 | char *content = read_file("test.txt", &size); |
| 26 | if (content) { |
| 27 | printf("Read %ld bytes:\n%s\n", size, content); |
| 28 | free(content); |
| 29 | } |
| 30 | return 0; |
| 31 | } |
These functions let you jump to arbitrary positions in a file — essential for random access, updating records in place, and parsing structured binary formats:
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | typedef struct { |
| 5 | int id; |
| 6 | char name[32]; |
| 7 | double balance; |
| 8 | } Account; |
| 9 | |
| 10 | int main(void) { |
| 11 | /* Create sample accounts */ |
| 12 | FILE *fp = fopen("accounts.dat", "w+b"); |
| 13 | Account accounts[] = { |
| 14 | { 101, "Alice", 1000.00 }, |
| 15 | { 102, "Bob", 2500.50 }, |
| 16 | { 103, "Carol", 750.25 }, |
| 17 | }; |
| 18 | fwrite(accounts, sizeof(Account), 3, fp); |
| 19 | |
| 20 | /* Read account #102 (second record) */ |
| 21 | fseek(fp, 1 * sizeof(Account), SEEK_SET); |
| 22 | Account acc; |
| 23 | fread(&acc, sizeof(Account), 1, fp); |
| 24 | printf("Read: %s balance=%.2f\n", acc.name, acc.balance); |
| 25 | |
| 26 | /* Update Bob's balance */ |
| 27 | acc.balance += 500.00; |
| 28 | fseek(fp, 1 * sizeof(Account), SEEK_SET); |
| 29 | fwrite(&acc, sizeof(Account), 1, fp); |
| 30 | |
| 31 | /* Verify */ |
| 32 | rewind(fp); |
| 33 | fseek(fp, 1 * sizeof(Account), SEEK_SET); |
| 34 | fread(&acc, sizeof(Account), 1, fp); |
| 35 | printf("Updated: %s balance=%.2f\n", acc.name, acc.balance); |
| 36 | |
| 37 | fclose(fp); |
| 38 | return 0; |
| 39 | } |
| Function | Description |
|---|---|
| fseek(fp, offset, whence) | Move to absolute or relative position |
| ftell(fp) | Return current position |
| rewind(fp) | Move to beginning (calls fseek(0, SEEK_SET)) |
| fgetpos(fp, pos) | Save current position into fpos_t |
| fsetpos(fp, pos) | Restore a previously saved position |
| Aspect | Text Mode | Binary Mode |
|---|---|---|
| Line endings | Translated (platform-specific) | No translation |
| Use case | Human-readable files | Images, structs, protocols |
| fseek reliability | Only ftell for position | Exact byte offsets |
| Mode string | "r", "w", "a" | "rb", "wb", "ab" |
| Conversion | May convert newlines | Raw bytes |
warning
Writing and reading structs directly to files creates compact, fast-loading data formats. The key concerns are padding, endianness, and struct size across platforms:
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | |
| 4 | typedef struct __attribute__((packed)) { |
| 5 | uint32_t id; |
| 6 | float x, y; |
| 7 | char label[12]; |
| 8 | } PackedRecord; |
| 9 | |
| 10 | void write_records(const char *path) { |
| 11 | FILE *fp = fopen(path, "wb"); |
| 12 | PackedRecord recs[] = { |
| 13 | { 1, 1.0f, 2.0f, "alpha" }, |
| 14 | { 2, 3.0f, 4.0f, "beta" }, |
| 15 | { 3, 5.0f, 6.0f, "gamma" }, |
| 16 | }; |
| 17 | fwrite(recs, sizeof(PackedRecord), 3, fp); |
| 18 | fclose(fp); |
| 19 | } |
| 20 | |
| 21 | void read_records(const char *path) { |
| 22 | FILE *fp = fopen(path, "rb"); |
| 23 | PackedRecord rec; |
| 24 | while (fread(&rec, sizeof(PackedRecord), 1, fp) == 1) { |
| 25 | printf("#%u (%.1f, %.1f) %s\n", rec.id, rec.x, rec.y, rec.label); |
| 26 | } |
| 27 | fclose(fp); |
| 28 | } |
| 29 | |
| 30 | int main(void) { |
| 31 | write_records("packed.dat"); |
| 32 | read_records("packed.dat"); |
| 33 | return 0; |
| 34 | } |
The standard library provides functions for temporary files, file deletion, and renaming. These are essential for safe write patterns (write-to-temp, then atomic rename):
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | void safe_write(const char *path, const char *data) { |
| 5 | /* Write to temp file first */ |
| 6 | FILE *tmp = tmpfile(); |
| 7 | if (!tmp) { perror("tmpfile"); return; } |
| 8 | |
| 9 | fputs(data, tmp); |
| 10 | |
| 11 | /* In production: copy tmp contents to path via rename() */ |
| 12 | /* For this example, we just write directly */ |
| 13 | FILE *fp = fopen(path, "w"); |
| 14 | if (fp) { |
| 15 | rewind(tmp); |
| 16 | char buf[4096]; |
| 17 | size_t n; |
| 18 | while ((n = fread(buf, 1, sizeof(buf), tmp)) > 0) { |
| 19 | fwrite(buf, 1, n, fp); |
| 20 | } |
| 21 | fclose(fp); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | int main(void) { |
| 26 | safe_write("output.txt", "Safe content\n"); |
| 27 | |
| 28 | /* rename() for atomic replacement */ |
| 29 | rename("output.txt.final", "output.txt"); |
| 30 | |
| 31 | /* remove() to delete a file */ |
| 32 | remove("output.txt"); |
| 33 | |
| 34 | return 0; |
| 35 | } |
Every C program starts with three open streams.freopen redirects a stream to a file — useful for logging or capturing user input:
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int main(void) { |
| 4 | /* Redirect stdout to a log file */ |
| 5 | FILE *log = freopen("app.log", "w", stdout); |
| 6 | if (!log) { perror("freopen"); return 1; } |
| 7 | |
| 8 | printf("This goes to app.log\n"); |
| 9 | printf("So does this\n"); |
| 10 | |
| 11 | /* Restore stdout */ |
| 12 | freopen("/dev/tty", "w", stdout); |
| 13 | /* On Windows: freopen("CON", "w", stdout); */ |
| 14 | |
| 15 | printf("This goes to the terminal\n"); |
| 16 | |
| 17 | fclose(log); |
| 18 | return 0; |
| 19 | } |
feof returns true after a read attempt fails at end-of-file. ferrorreturns true if any error occurred on the stream. The correct pattern for reading is to check the return value of the read function, notfeof:
| 1 | #include <stdio.h> |
| 2 | |
| 3 | int process_file(const char *path) { |
| 4 | FILE *fp = fopen(path, "r"); |
| 5 | if (!fp) return -1; |
| 6 | |
| 7 | int ch; |
| 8 | while ((ch = fgetc(fp)) != EOF) { |
| 9 | /* process character */ |
| 10 | } |
| 11 | |
| 12 | if (ferror(fp)) { |
| 13 | fprintf(stderr, "Error reading %s\n", path); |
| 14 | fclose(fp); |
| 15 | return -1; |
| 16 | } |
| 17 | |
| 18 | if (!feof(fp)) { |
| 19 | fprintf(stderr, "Unexpected end\n"); |
| 20 | fclose(fp); |
| 21 | return -1; |
| 22 | } |
| 23 | |
| 24 | fclose(fp); |
| 25 | return 0; |
| 26 | } |
warning
| 1 | #include <stdio.h> |
| 2 | #include <ctype.h> |
| 3 | |
| 4 | typedef struct { |
| 5 | long chars; |
| 6 | long words; |
| 7 | long lines; |
| 8 | } WordCount; |
| 9 | |
| 10 | WordCount count_file(const char *path) { |
| 11 | WordCount wc = { 0, 0, 0 }; |
| 12 | FILE *fp = fopen(path, "r"); |
| 13 | if (!fp) return wc; |
| 14 | |
| 15 | int ch, in_word = 0; |
| 16 | while ((ch = fgetc(fp)) != EOF) { |
| 17 | wc.chars++; |
| 18 | if (ch == '\n') wc.lines++; |
| 19 | if (isspace(ch)) { |
| 20 | in_word = 0; |
| 21 | } else if (!in_word) { |
| 22 | in_word = 1; |
| 23 | wc.words++; |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | fclose(fp); |
| 28 | return wc; |
| 29 | } |
| 30 | |
| 31 | int main(void) { |
| 32 | WordCount wc = count_file("article.txt"); |
| 33 | printf("Chars: %ld, Words: %ld, Lines: %ld\n", |
| 34 | wc.chars, wc.words, wc.lines); |
| 35 | return 0; |
| 36 | } |
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | #include <stdlib.h> |
| 4 | |
| 5 | #define MAX_LINE 1024 |
| 6 | #define MAX_FIELDS 32 |
| 7 | |
| 8 | typedef struct { |
| 9 | char *fields[MAX_FIELDS]; |
| 10 | int field_count; |
| 11 | } CSVRow; |
| 12 | |
| 13 | void free_csv_row(CSVRow *row) { |
| 14 | for (int i = 0; i < row->field_count; i++) |
| 15 | free(row->fields[i]); |
| 16 | row->field_count = 0; |
| 17 | } |
| 18 | |
| 19 | int parse_csv_line(const char *line, CSVRow *row) { |
| 20 | row->field_count = 0; |
| 21 | const char *start = line; |
| 22 | |
| 23 | while (*start && *start != '\n' && *start != '\r') { |
| 24 | if (*start == ',') { |
| 25 | start++; |
| 26 | continue; |
| 27 | } |
| 28 | |
| 29 | const char *end = start; |
| 30 | while (*end && *end != ',' && *end != '\n' && *end != '\r') |
| 31 | end++; |
| 32 | |
| 33 | size_t len = end - start; |
| 34 | row->fields[row->field_count] = malloc(len + 1); |
| 35 | memcpy(row->fields[row->field_count], start, len); |
| 36 | row->fields[row->field_count][len] = '\0'; |
| 37 | row->field_count++; |
| 38 | |
| 39 | start = end; |
| 40 | if (row->field_count >= MAX_FIELDS) break; |
| 41 | } |
| 42 | return row->field_count; |
| 43 | } |
| 44 | |
| 45 | void process_csv(const char *path) { |
| 46 | FILE *fp = fopen(path, "r"); |
| 47 | if (!fp) { perror("fopen"); return; } |
| 48 | |
| 49 | char line[MAX_LINE]; |
| 50 | int header_done = 0; |
| 51 | |
| 52 | while (fgets(line, sizeof(line), fp)) { |
| 53 | CSVRow row = { 0 }; |
| 54 | parse_csv_line(line, &row); |
| 55 | |
| 56 | if (!header_done) { |
| 57 | printf("Headers: "); |
| 58 | for (int i = 0; i < row.field_count; i++) |
| 59 | printf("[%s] ", row.fields[i]); |
| 60 | printf("\n"); |
| 61 | header_done = 1; |
| 62 | } else { |
| 63 | printf("Row: "); |
| 64 | for (int i = 0; i < row.field_count; i++) |
| 65 | printf("%s ", row.fields[i]); |
| 66 | printf("\n"); |
| 67 | } |
| 68 | |
| 69 | free_csv_row(&row); |
| 70 | } |
| 71 | |
| 72 | fclose(fp); |
| 73 | } |
| 74 | |
| 75 | int main(void) { |
| 76 | /* Create sample CSV */ |
| 77 | FILE *fp = fopen("data.csv", "w"); |
| 78 | fprintf(fp, "name,age,city\n"); |
| 79 | fprintf(fp, "Alice,30,NYC\n"); |
| 80 | fprintf(fp, "Bob,25,LA\n"); |
| 81 | fprintf(fp, "Carol,35,Chicago\n"); |
| 82 | fclose(fp); |
| 83 | |
| 84 | process_csv("data.csv"); |
| 85 | remove("data.csv"); |
| 86 | return 0; |
| 87 | } |
| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | |
| 4 | #define COPY_BUF_SIZE (64 * 1024) |
| 5 | |
| 6 | int copy_file(const char *src, const char *dst) { |
| 7 | FILE *in = fopen(src, "rb"); |
| 8 | FILE *out = fopen(dst, "wb"); |
| 9 | if (!in || !out) { |
| 10 | perror("fopen"); |
| 11 | if (in) fclose(in); |
| 12 | if (out) fclose(out); |
| 13 | return -1; |
| 14 | } |
| 15 | |
| 16 | char buf[COPY_BUF_SIZE]; |
| 17 | size_t n; |
| 18 | long total = 0; |
| 19 | |
| 20 | while ((n = fread(buf, 1, COPY_BUF_SIZE, in)) > 0) { |
| 21 | size_t written = fwrite(buf, 1, n, out); |
| 22 | if (written != n) { |
| 23 | fprintf(stderr, "Write error after %ld bytes\n", total); |
| 24 | fclose(in); |
| 25 | fclose(out); |
| 26 | return -1; |
| 27 | } |
| 28 | total += (long)n; |
| 29 | } |
| 30 | |
| 31 | if (ferror(in)) { |
| 32 | fprintf(stderr, "Read error\n"); |
| 33 | fclose(in); |
| 34 | fclose(out); |
| 35 | return -1; |
| 36 | } |
| 37 | |
| 38 | fclose(in); |
| 39 | fclose(out); |
| 40 | printf("Copied %ld bytes\n", total); |
| 41 | return 0; |
| 42 | } |
| 43 | |
| 44 | int main(void) { |
| 45 | copy_file("source.bin", "dest.bin"); |
| 46 | return 0; |
| 47 | } |
File I/O introduces security risks. Path traversal, buffer overflows from untrusted file names, and denial-of-service through giant files must all be addressed:
| 1 | #include <stdio.h> |
| 2 | #include <string.h> |
| 3 | #include <limits.h> |
| 4 | |
| 5 | /* Reject paths with traversal sequences */ |
| 6 | int is_safe_path(const char *path) { |
| 7 | if (strstr(path, "..")) return 0; |
| 8 | if (strstr(path, "//")) return 0; |
| 9 | #ifdef _WIN32 |
| 10 | if (strstr(path, "\\")) return 0; |
| 11 | if (path[1] == ':') return 0; /* absolute drive letter */ |
| 12 | #else |
| 13 | if (path[0] == '/') return 0; /* reject absolute paths */ |
| 14 | #endif |
| 15 | return 1; |
| 16 | } |
| 17 | |
| 18 | /* Safe file read with size limit */ |
| 19 | char *safe_read(const char *path, long max_size) { |
| 20 | if (!is_safe_path(path)) { |
| 21 | fprintf(stderr, "Unsafe path: %s\n", path); |
| 22 | return NULL; |
| 23 | } |
| 24 | |
| 25 | FILE *fp = fopen(path, "rb"); |
| 26 | if (!fp) return NULL; |
| 27 | |
| 28 | fseek(fp, 0, SEEK_END); |
| 29 | long size = ftell(fp); |
| 30 | rewind(fp); |
| 31 | |
| 32 | if (size > max_size || size < 0) { |
| 33 | fclose(fp); |
| 34 | return NULL; |
| 35 | } |
| 36 | |
| 37 | char *buf = malloc(size + 1); |
| 38 | if (!buf) { fclose(fp); return NULL; } |
| 39 | |
| 40 | size_t n = fread(buf, 1, size, fp); |
| 41 | buf[n] = '\0'; |
| 42 | fclose(fp); |
| 43 | return buf; |
| 44 | } |
warning
Cross-platform file I/O must handle endianness, line endings, and struct padding differences. Use network byte order for binary data and always open binary files with"rb" / "wb":
| 1 | #include <stdio.h> |
| 2 | #include <stdint.h> |
| 3 | |
| 4 | /* Endian-safe read/write for portable binary files */ |
| 5 | uint32_t read_u32_be(FILE *fp) { |
| 6 | uint8_t buf[4]; |
| 7 | if (fread(buf, 1, 4, fp) != 4) return 0; |
| 8 | return ((uint32_t)buf[0] << 24) | |
| 9 | ((uint32_t)buf[1] << 16) | |
| 10 | ((uint32_t)buf[2] << 8) | |
| 11 | ((uint32_t)buf[3]); |
| 12 | } |
| 13 | |
| 14 | void write_u32_be(FILE *fp, uint32_t val) { |
| 15 | uint8_t buf[4]; |
| 16 | buf[0] = (val >> 24) & 0xFF; |
| 17 | buf[1] = (val >> 16) & 0xFF; |
| 18 | buf[2] = (val >> 8) & 0xFF; |
| 19 | buf[3] = (val) & 0xFF; |
| 20 | fwrite(buf, 1, 4, fp); |
| 21 | } |
| 22 | |
| 23 | int main(void) { |
| 24 | FILE *fp = fopen("portable.bin", "wb"); |
| 25 | write_u32_be(fp, 0xDEADBEEF); |
| 26 | fclose(fp); |
| 27 | |
| 28 | fp = fopen("portable.bin", "rb"); |
| 29 | uint32_t val = read_u32_be(fp); |
| 30 | printf("Read: 0x%08X\n", val); /* 0xDEADBEEF on any platform */ |
| 31 | fclose(fp); |
| 32 | return 0; |
| 33 | } |
info