Understanding File Operations and Stream Management in C

Files provide persistent data storage, unlike runtime variables that vanish when a program exits. The ability to persist data across program executions and retrieve it later is fundamental to most software applications. In C, file operations work through a unified interface centered around the concept of streams.

The Stream Abstraction

A stream is an abstraction that represents a flow of data between a program and an external device. Whether data moves to a disk file, the console, a printer, or network interface, C exposes all these destinations through a consistent API. The FILE type, defined in the standard library, represents a stream object. Each FILE object corresponds to an open stream and maintains state about the associated physical device.

typedef struct _IO_FILE FILE;

The standard library provides FILE * pointers for three pre-defined streams:

Stream Description
stdin Standard input, typically keyboard
stdout Standard output, typically screen
stderr Standard error output, typically screen

These streams require no explicit opening or closing—they are automatically available when a program starts.

File Access Functions

Opening and Closing Files

The fopen() function opens a file and associates it with a stream:

FILE *fopen(const char *filename, const char *mode);

File modes determine how the file is accessed:

Mode Description
r Open text file for reading
w Create or truncate to zero length for writing
a Append; open or create for writing at end of file
r+ Open for both reading and writing
w+ Create for both reading and writing; truncate if exists
a+ Open or create for reading and appending

Binary file modes add a b suffix: rb, wb, ab, rb+, wb+, ab+.

Closing a file flushes any buffered data and releases resources:

int fclose(FILE *stream);
#include <stdio.h>

int main(void) {
    FILE *handle = fopen("data.txt", "w");
    
    if (handle == NULL) {
        perror("Cannot open file");
        return 1;
    }
    
    fprintf(handle, "Sample data: %d\n", 42);
    fclose(handle);
    return 0;
}

Path and Encoding Considerations: On Windows, fopen() uses the system ANSI code page by default. For paths containing non-ASCII characters, use the wide-character variant _wfopen():

FILE *handle = _wfopen(L"C:\\用户\\文档\\data.txt", L"r");

Unicode file handling with explicit encoding:

FILE *handle = fopen("utf8file.txt", "r, ccs=UTF-8");

Supported encodings include UTF-8, UTF-16LE, and UNICODE.

Character-Based I/O

Reading and Writing Individual Characters

int fgetc(FILE *stream);    // Read one character
int fputc(int ch, FILE *stream);  // Write one character
int getc(FILE *stream);    // Often a macro version of fgetc
int putc(int ch, FILE *stream);   // Often a macro version of fputc

The getchar() and putchar() functions operate on stdin and stdout respectively.

The ungetc() function pushes a character back onto the stream for subsequent reading:

int ungetc(int c, FILE *stream);
#include <stdio.h>

int main(void) {
    FILE *src = fopen("input.txt", "r");
    if (src == NULL) {
        return 1;
    }
    
    int byte;
    while ((byte = fgetc(src)) != EOF) {
        putchar(byte);
    }
    
    fclose(src);
    return 0;
}
#include <stdio.h>

int main(void) {
    FILE *dest = fopen("output.txt", "w");
    if (dest == NULL) {
        return 1;
    }
    
    const char *text = "HELLO";
    while (*text) {
        fputc(*text++, dest);
    }
    fputc('\n', dest);
    
    fclose(dest);
    return 0;
}

For wide characters (required for multibyte characters like Chinece):

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(void) {
    setlocale(LC_ALL, "zh_CN.GBK");
    
    FILE *src = fopen("chinese_text.txt", "r, ccs=GBK");
    if (src == NULL) {
        return 1;
    }
    
    wint_t ch;
    while ((ch = fgetwc(src)) != WEOF) {
        putwchar(ch);
    }
    
    fclose(src);
    return 0;
}

String-Based I/O

char *fgets(char *buffer, int n, FILE *stream);
int fputs(const char *str, FILE *stream);

fgets() reads up to n-1 characters, stopping at newline or EOF, and always terminates with a null character.

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("log.txt", "w");
    if (fp == NULL) return 1;
    
    fputs("Log entry one\n", fp);
    fputs("Log entry two\n", fp);
    
    fclose(fp);
    
    fp = fopen("log.txt", "r");
    if (fp == NULL) return 1;
    
    char line[256];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%s", line);
    }
    
    fclose(fp);
    return 0;
}

Block Data Transfer

For binary data structures or large amounts of data:

size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
#include <stdio.h>

typedef struct {
    int id;
    double value;
    char label[32];
} Record;

int main(void) {
    Record data[3] = {
        {1, 3.14, "alpha"},
        {2, 2.71, "beta"},
        {3, 1.41, "gamma"}
    };
    
    FILE *out = fopen("records.bin", "wb");
    if (out == NULL) return 1;
    
    fwrite(data, sizeof(Record), 3, out);
    fclose(out);
    
    Record read_back[3] = {0};
    FILE *in = fopen("records.bin", "rb");
    if (in == NULL) return 1;
    
    size_t n = fread(read_back, sizeof(Record), 3, in);
    fclose(in);
    
    for (size_t i = 0; i < n; i++) {
        printf("ID: %d, Value: %.2f, Label: %s\n",
               read_back[i].id, read_back[i].value, read_back[i].label);
    }
    
    return 0;
}

File Positioning

int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);

int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);

The whence argument for fseek:

Constant Origin Point
SEEK_SET Beginning of file
SEEK_CUR Current position
SEEK_END End of file
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.bin", "r+b");
    if (fp == NULL) return 1;
    
    printf("Initial position: %ld\n", ftell(fp));
    
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);
    printf("File size: %ld bytes\n", file_size);
    
    rewind(fp);
    printf("After rewind: %ld\n", ftell(fp));
    
    fseek(fp, 16, SEEK_SET);
    printf("After seek to offset 16: %ld\n", ftell(fp));
    
    fclose(fp);
    return 0;
}

Formatted I/O

int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("metrics.txt", "w");
    if (fp == NULL) return 1;
    
    fprintf(fp, "Count: %d\n", 100);
    fprintf(fp, "Ratio: %.4f\n", 0.9375);
    fprintf(fp, "Status: %s\n", "OK");
    
    fclose(fp);
    
    fp = fopen("metrics.txt", "r");
    if (fp == NULL) return 1;
    
    int count;
    float ratio;
    char status[32];
    
    fscanf(fp, "Count: %d", &count);
    fscanf(fp, "Ratio: %f", &ratio);
    fscanf(fp, "Status: %s", status);
    
    printf("Read: count=%d, ratio=%.4f, status=%s\n", count, ratio, status);
    
    fclose(fp);
    return 0;
}

Error Detection and Handling

int feof(FILE *stream);      // Returns non-zero at end-of-file
int ferror(FILE *stream);    // Returns non-zero if error occurred
void clearerr(FILE *stream); // Clear EOF and error indicators
void perror(const char *msg); // Print error message to stderr
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("nonexistent.dat", "r");
    if (fp == NULL) {
        perror("File open failed");
        return 1;
    }
    
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }
    
    if (feof(fp)) {
        printf("\nEnd of file reached normally\n");
    } else if (ferror(fp)) {
        printf("\nRead error occurred\n");
        clearerr(fp);
    }
    
    fclose(fp);
    return 0;
}

Buffering

C's standard I/O library uses memory buffers to improve performance by reducing system calls.

Buffer Types

Type Flush Trigger Typical Use
Unbuffered Immediate, no buffering Error streams (stderr)
Line buffered Newline or buffer full Terminal output
Fully buffered Buffer full or explicit fflush Disk files

Buffer Management

int setvbuf(FILE *stream, char *buf, int mode, size_t size);
void setbuf(FILE *stream, char *buf);
int fflush(FILE *stream);

Buffer modes: _IONBF (unbuffered), _IOLBF (line buffered), _IOFBF (fully buffered).

#include <stdio.h>

int main(void) {
    FILE *fp = fopen("buffered_output.txt", "w");
    if (fp == NULL) return 1;
    
    char custom_buffer[4096];
    setvbuf(fp, custom_buffer, _IOFBF, sizeof(custom_buffer));
    
    fputs("This string is buffered.\n", fp);
    fflush(fp);  // Force immediate write
    
    fclose(fp);
    return 0;
}

Stream Redirection with freopen

The freopen() function redirects an existing stream:

FILE *freopen(const char *filename, const char *mode, FILE *stream);
#include <stdio.h>

int main(void) {
    FILE *original_stdin = stdin;
    
    FILE *infile = freopen("commands.txt", "r", stdin);
    if (infile == NULL) {
        perror("Redirection failed");
        return 1;
    }
    
    int value;
    while (scanf("%d", &value) == 1) {
        printf("Processing: %d\n", value);
    }
    
    freopen("error.log", "w", stderr);
    fprintf(stderr, "Operation completed\n");
    
    return 0;
}

Working with Binary File Formats

BMP Image Files

BMP files have a structured header followed by pixel data. Values are stored in little-endian byte order.

#include <stdio.h>
#include <stdint.h>

#pragma pack(push, 1)
typedef struct {
    uint8_t  signature[2];      // "BM"
    uint32_t file_size;
    uint16_t reserved1;
    uint16_t reserved2;
    uint32_t data_offset;
    uint32_t header_size;
    int32_t  width;
    int32_t  height;
    uint16_t planes;
    uint16_t bits_per_pixel;
    uint32_t compression;
    uint32_t image_size;
    int32_t  x_pixels_per_meter;
    int32_t  y_pixels_per_meter;
    uint32_t colors_used;
    uint32_t important_colors;
} BMPHeader;
#pragma pack(pop)

uint32_t little_to_host(uint8_t *bytes, size_t len) {
    uint32_t result = 0;
    int started = 0;
    for (int i = 0; i < (int)len; i++) {
        if (!started && bytes[len - i - 1]) started = 1;
        if (started) {
            result = (result << 8) | bytes[len - i - 1];
        }
    }
    return result;
}
int read_bmp_info(const char *path, BMPHeader *header) {
    FILE *fp = fopen(path, "rb");
    if (fp == NULL) return -1;
    
    uint8_t raw[54];
    if (fread(raw, sizeof(raw), 1, fp) != 1) {
        fclose(fp);
        return -2;
    }
    
    header->signature[0] = raw[0];
    header->signature[1] = raw[1];
    header->file_size = little_to_host(raw + 2, 4);
    header->width = (int32_t)little_to_host(raw + 18, 4);
    header->height = (int32_t)little_to_host(raw + 22, 4);
    header->bits_per_pixel = little_to_host(raw + 28, 2);
    header->data_offset = little_to_host(raw + 10, 4);
    
    fclose(fp);
    return 0;
}

DOCX and Office Documents

Modern Office formats (.docx, .xlsx, .pptx) are ZIP archives containing XML files. To work with them:

  1. Unzip the archive
  2. Extract and parse the XML content
  3. Repackage after modifications

Terminal Control Sequences

ANSI escape sequences control console formatting, colors, and cursor movement. All sequences begin with ESC (\x1b) followed by [.

Cursor Positioning

#define ESC "\x1b["

void move_cursor(int row, int col) {
    printf(ESC"%d;%dH", row, col);
}

void move_up(int n)    { printf(ESC"%dA", n); }
void move_down(int n)  { printf(ESC"%dB", n); }
void move_right(int n) { printf(ESC"%dC", n); }
void move_left(int n)  { printf(ESC"%dD", n); }

Cursor Visibility

void hide_cursor(void)  { printf(ESC"?25l"); }
void show_cursor(void) { printf(ESC"?25h"); }

Text Formatting

void clear_screen(void)   { printf(ESC"2J"); }
void clear_line_end(void) { printf(ESC"0K"); }
void clear_line_start(void){ printf(ESC"1K"); }
void clear_line(void)     { printf(ESC"2K"); }

Text Colors (SGR Sequences)

#define RESET      "0"
#define BOLD       "1"
#define UNDERLINE  "4"

// Foreground colors
#define FG_BLACK   "30"
#define FG_RED     "31"
#define FG_GREEN   "32"
#define FG_YELLOW  "33"
#define FG_BLUE    "34"
#define FG_MAGENTA "35"
#define FG_CYAN    "36"
#define FG_WHITE   "37"

// Background colors
#define BG_BLACK   "40"
#define BG_RED     "41"
#define BG_GREEN   "42"
#define BG_YELLOW  "43"
#define BG_BLUE    "44"
#define BG_MAGENTA "45"
#define BG_CYAN    "46"
#define BG_WHITE   "47"

void set_color(const char *fg, const char *bg) {
    printf(ESC"%s;%sm", fg, bg);
}

void set_rgb_fg(int r, int g, int b) {
    printf(ESC"38;2;%d;%d;%dm", r, g, b);
}

void set_rgb_bg(int r, int g, int b) {
    printf(ESC"48;2;%d;%d;%dm", r, g, b);
}
int main(void) {
    printf("Normal text\n");
    printf(ESC"1;31m" "Bold red\n" ESC"0m");
    printf(ESC"32;44m" "Green on blue\n" ESC"0m");
    
    set_rgb_fg(255, 165, 0);
    printf("Orange text\n");
    printf(ESC"0m");
    
    return 0;
}

Terminal Window Title

void set_window_title(const char *title) {
    printf("\x1b]0;%s\x07", title);
}

Console Graphics

Combining RGB color control with BMP parsing enables rendering images in the terminal:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int width;
    int height;
    int offset;
    unsigned char *pixels;
} ImageData;

void set_pixel_rgb(int r, int g, int b) {
    printf("\x1b[48;2;%d;%d;%dm ", r, g, b);
    fflush(stdout);
}

void reset_formatting(void) {
    printf("\x1b[0m\n");
}

int load_bmp(const char *path, ImageData *img) {
    FILE *fp = fopen(path, "rb");
    if (!fp) return -1;
    
    unsigned char header[54];
    fread(header, 54, 1, fp);
    
    if (header[0] != 'B' || header[1] != 'M') {
        fclose(fp);
        return -2;
    }
    
    img->width = *(int*)(header + 18);
    img->height = *(int*)(header + 22);
    img->offset = *(int*)(header + 10);
    
    fseek(fp, img->offset, SEEK_SET);
    
    int row_size = img->width * 3;
    int padding = (4 - (row_size % 4)) % 4;
    int actual_row = row_size + padding;
    
    unsigned char *raw = malloc(actual_row * img->height);
    fread(raw, actual_row * img->height, 1, fp);
    fclose(fp);
    
    img->pixels = malloc(img->width * img->height * 3);
    
    for (int y = img->height - 1; y >= 0; y--) {
        for (int x = 0; x < img->width; x++) {
            int src_idx = y * actual_row + x * 3;
            int dst_idx = (img->height - 1 - y) * img->width * 3 + x * 3;
            img->pixels[dst_idx] = raw[src_idx];
            img->pixels[dst_idx + 1] = raw[src_idx + 1];
            img->pixels[dst_idx + 2] = raw[src_idx + 2];
        }
    }
    
    free(raw);
    return 0;
}

void render_image(ImageData *img) {
    printf("\x1b[?25l");  // Hide cursor
    
    for (int y = 0; y < img->height; y++) {
        for (int x = 0; x < img->width; x++) {
            int idx = y * img->width * 3 + x * 3;
            set_pixel_rgb(img->pixels[idx + 2],
                         img->pixels[idx + 1],
                         img->pixels[idx]);
        }
        reset_formatting();
    }
    
    printf("\x1b[?25h");  // Show cursor
}

This approach renders BMP images in the console by setting the background color of each character cell. For best results, configure the terminal with a small font size to approximate pixel-level control.

Summary of File API Functions

Function Purpose
fopen Open file
fclose Close file
fgetc/fputc Read/write single character
fgets/fputs Read/write string
fread/fwrite Read/write blocks of data
fprintf/fscanf Formatted I/O
fseek Move file pointer
ftell Get current position
rewind Return to file beginning
feof Check for end of file
ferror Check for errors
fflush Force write buffered data
freopen Redirect stream
setvbuf Configure buffering

Tags: c programming File I/O Streams FILE type Binary Files

Posted on Thu, 14 May 2026 22:12:32 +0000 by helraizer