Lesson 08-C Language File Operations

File Opening and Closing

File Opening

The fopen() function is used to open a file, with the following prototype:

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

filename: A pointer to a string containing the file name.
mode: Specifies the file opening mode. Common modes include:

  • "r": Read-only mode; the file must exist.
  • "w": Write mode; if the file exists, it will be truncated; if it does not exist, a new file will be created.
  • "a": Append mode; the file pointer is positioned at the end of the file; if the file does not exist, a new file will be created.
  • "r+": Read-write mode; the file must exist.
  • "w+": Read-write mode; if the file exists, it will be truncated; if it does not exist, a new file will be created.
  • "a+": Read-write append mode; the file pointer is positioned at the end of the file; if the file does not exist, a new file will be created.
  • "rb", "wb", "ab", "rb+", "wb+", "ab+": Same as above, but for binary files.

fopen() returns a pointer to a FILE structure upon successful opening, or NULL on failure.

Common Opening Modes Table

Mode StringDescription
"r"Read-only mode (text file): The file must exist, otherwise opening fails.
"w"Write-only mode (text file): Creates the file if it doesn’t exist; truncates if it does.
"a"Append mode (text file): Creates the file if it doesn’t exist; appends to the end if it does.
"rb"Read-only mode (binary file): Same as "r", but reads/writes in binary mode.
"wb"Write-only mode (binary file): Same as "w", but reads/writes in binary mode.
"ab"Append mode (binary file): Same as "a", but reads/writes in binary mode.
"r+"Read-write mode (text file): The file must exist, supports reading and writing.
"w+"Read-write mode (text file): Creates the file if it doesn’t exist; truncates if it does, supports reading and writing.
"a+"Read-write append mode (text file): Creates the file if it doesn’t exist; appends to the end if it does, supports reading and writing.
"rb+"Read-write mode (binary file): Same as "r+", but reads/writes in binary mode.
"wb+"Read-write mode (binary file): Same as "w+", but reads/writes in binary mode.
"ab+"Read-write append mode (binary file): Same as "a+", but reads/writes in binary mode.

Example: Opening a File

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

int main() {
    FILE* fp;

    // Open text file (read-only)
    fp = fopen("data.txt", "r");
    if (fp == NULL) {  // Check if opening succeeded
        perror("Failed to open file");  // Output error message (e.g., "No such file or directory")
        exit(EXIT_FAILURE);             // Exit program
    }
    printf("File opened (read-only mode)\n");

    // Open binary file (read-write, create if not exists)
    FILE* bin_fp = fopen("data.bin", "wb+");
    if (bin_fp == NULL) {
        perror("Failed to open binary file");
        fclose(fp);  // Close already opened file (avoid resource leak)
        exit(EXIT_FAILURE);
    }
    printf("Binary file opened (read-write mode)\n");

    // Close files (detailed in later sections)
    fclose(fp);
    fclose(bin_fp);
    return 0;
}

Notes

  • Mode Selection: Binary files (e.g., images, executables) must use "rb", "wb", etc., to avoid garbled data or corruption.
  • File Existence: "r", "rb" require the file to exist, otherwise fopen returns NULL; "w", "wb" overwrite existing files—use with caution.
  • Error Handling: Always check the return value of fopen (NULL indicates failure), and use perror or strerror(errno) to locate the specific error.

File Closing

The fclose() function is used to close an already opened file, with the following prototype:

int fclose(FILE *stream);
  • stream: A pointer to the FILE structure returned by fopen(). fclose() returns 0 on success and EOF (usually defined as -1) on failure.

Example Code Below is a simple example using fopen() and fclose():

#include <stdio.h>

int main() {
    FILE *file;
    file = fopen("example.txt", "w"); // Open or create a file named example.txt in write mode

    if (file == NULL) {
        printf("Error opening file.\n");
        return 1;
    }

    fprintf(file, "Hello, world!\n"); // Write text to the file

    fclose(file); // Close the file

    return 0;
}

In this example, we first attempt to open or create a file named example.txt in write mode ("w"). If the file is successfully opened, we write some text to it and then close the file.

Notes

  • Always call fclose() after using a file to ensure all buffered data is written to disk and resources are released.
  • Forgetting to close a file may result in data loss or program resource leaks.
  • When a file is opened in write mode ("w"), existing content is cleared; in append mode ("a"), the file pointer is positioned at the end without clearing content.

File Read and Write Operations

File reading and writing are the core of file operations. C provides multiple functions, including byte-oriented fread/fwrite and format-oriented fscanf/fprintf.

Byte-Oriented Read/Write (fread/fwrite)

fread: Read Binary Data

fread is used to read a specified number of bytes of binary data from a file, with the following prototype:

size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
  • ptr: Memory buffer to store the read data (pointer).
  • size: Size in bytes of each data element (e.g., sizeof(int)).
  • nmemb: Number of elements to read.
  • stream: File pointer (FILE*).
  • Return value: Number of elements actually read (if less than nmemb, may have reached end of file or an error occurred).

Example: Reading a Binary File

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

int main() {
    FILE* bin_fp = fopen("data.bin", "rb");
    if (bin_fp == NULL) { perror("Open failed"); exit(EXIT_FAILURE); }

    // Read 10 integers (each 4 bytes)
    int buffer[10];
    size_t read_count = fread(buffer, sizeof(int), 10, bin_fp);

    if (read_count < 10) {
        if (feof(bin_fp)) {
            printf("Reached end of file, actually read %zu integers\n", read_count);
        } else if (ferror(bin_fp)) {
            perror("Read error");
        }
    } else {
        printf("Successfully read 10 integers\n");
    }

    fclose(bin_fp);
    return 0;
}

fwrite: Write Binary Data

fwrite is used to write a specified number of bytes of binary data to a file, with the following prototype:

size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);
  • Parameter meanings: Similar to fread, ptr is the buffer containing data to write.
  • Return value: Number of elements actually written (usually equals nmemb unless an error occurs).

Example: Writing a Binary File

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

int main() {
    FILE* bin_fp = fopen("data.bin", "wb");
    if (bin_fp == NULL) { perror("Open failed"); exit(EXIT_FAILURE); }

    int data[] = {1, 2, 3, 4, 5};
    size_t write_count = fwrite(data, sizeof(int), 5, bin_fp);

    if (write_count != 5) {
        perror("Write failed");
        fclose(bin_fp);
        exit(EXIT_FAILURE);
    }
    printf("Successfully wrote 5 integers\n");

    fclose(bin_fp);
    return 0;
}

Notes

  • Binary Mode: fread/fwrite must be used with "rb", "wb", etc., to avoid issues from newline conversion (e.g., \r\n on Windows).
  • Buffer Alignment: Ensure the memory buffer pointed to by ptr is aligned to size (e.g., address should be a multiple of 4 when reading int).
  • Partial Read/Write: Return value may be less than nmemb (e.g., end of file or disk full); check return value to handle exceptions.

Format-Oriented Read/Write (fscanf/fprintf)

fprintf: Formatted Text Output

fprintf is used to write formatted text data to a file (similar to printf, but outputs to a file), with the following prototype:

int fprintf(FILE* stream, const char* format, ...);
  • Parameter meanings: format is the format string (e.g., "%d %s %.2f\n"), followed by variables to format.
  • Return value: Number of characters successfully written (negative on error).

Example: Writing to a Text File

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

int main() {
    FILE* txt_fp = fopen("data.txt", "w");
    if (txt_fp == NULL) { perror("Open failed"); exit(EXIT_FAILURE); }

    int age = 20;
    char name[] = "Zhang San";
    float score = 85.5;

    // Write formatted text
    fprintf(txt_fp, "Name: %s, Age: %d, Score: %.2f\n", name, age, score);

    fclose(txt_fp);
    return 0;
}

fscanf: Formatted Text Input

fscanf is used to read formatted text data from a file (similar to scanf, but input comes from a file), with the following prototype:

int fscanf(FILE* stream, const char* format, ...);
  • Parameter meanings: format is the format string (e.g., "%d %s %f"), followed by pointers to store the results.
  • Return value: Number of input items successfully matched and assigned (EOF on end of file or error).

Example: Reading from a Text File

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

int main() {
    FILE* txt_fp = fopen("data.txt", "r");
    if (txt_fp == NULL) { perror("Open failed"); exit(EXIT_FAILURE); }

    char name[50];
    int age;
    float score;

    // Read formatted text
    int ret = fscanf(txt_fp, "Name: %s, Age: %d, Score: %f\n", name, &age, &score);
    if (ret == EOF) {
        if (feof(txt_fp)) {
            printf("File is empty or read failed\n");
        } else if (ferror(txt_fp)) {
            perror("Read error");
        }
    } else if (ret == 3) {  // Successfully matched 3 items
        printf("Read successful: Name=%s, Age=%d, Score=%.2f\n", name, age, score);
    } else {
        printf("Partial match (%d items), remaining data may have format errors\n", ret);
    }

    fclose(txt_fp);
    return 0;
}

Notes

  • Text Mode: fprintf/fscanf must be used with "r", "w", etc., to avoid format errors due to newline conversion.
  • Buffer Flush: After writing, call fflush(stream) to flush the output buffer if immediate disk write is needed (fclose flushes automatically).
  • String Length: When using %s to read strings, ensure the target buffer is large enough (avoid buffer overflow), or use %ns to limit length (e.g., %49s reads at most 49 characters).

File Positioning (fseek/ftell)

File positioning controls the file pointer’s position (offset from start, current position, or end), enabling random access (e.g., jumping to the middle of a file).

File Positioning Functions (fseek/ftell)

fseek: Move File Pointer

fseek moves the file pointer by a specified offset from a reference position, with the following prototype:

int fseek(FILE* stream, long int offset, int whence);
  • offset: Offset in bytes (can be positive or negative).
  • whence: Reference position (enumerated values):
    • SEEK_SET: From the beginning of the file (offset starts at 0).
    • SEEK_CUR: From the current file pointer position.
    • SEEK_END: From the end of the file (negative offset moves forward).
  • Return value: 0 on success, non-zero on failure (e.g., file not opened or offset out of range).

Example: Moving File Pointer

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

int main() {
    FILE* fp = fopen("data.txt", "r");
    if (fp == NULL) { perror("Open failed"); exit(EXIT_FAILURE); }

    // Move to the beginning of the file (equivalent to rewind(fp))
    fseek(fp, 0, SEEK_SET);

    // Move to the end of the file (to get file size)
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);  // See section 3.1.2
    printf("File size: %ld bytes\n", file_size);

    // Offset 10 bytes from the beginning
    fseek(fp, 10, SEEK_SET);
    char buffer[11];
    fgets(buffer, sizeof(buffer), fp);  // Read 10 characters starting from byte 10
    printf("Content after 10-byte offset: %s\n", buffer);

    fclose(fp);
    return 0;
}

ftell: Get Current File Pointer Position

ftell returns the current file pointer position relative to the start of the file, with the following prototype:

long int ftell(FILE* stream);
  • Return value: Current file pointer position in bytes (-1L on error).

Example: Getting File Size

FILE* fp = fopen("data.txt", "r");
if (fp == NULL) { /* Error handling */ }

fseek(fp, 0, SEEK_END);  // Move to end of file
long size = ftell(fp);   // Offset is now the file size
printf("File size: %ld bytes\n", size);

fseek(fp, 0, SEEK_SET);  // Move back to start

Notes

  • Binary Mode: fseek/ftell are more reliable in binary mode (text mode may cause incorrect offsets due to newline conversion).
  • Large File Support: On 32-bit systems, long may not represent offsets beyond 2GB (use fseeko/ftello with off_t type).
  • Error Checking: If fseek fails (e.g., offset too large), the file pointer position may be undefined; use ferror to check for errors.

Other File Positioning Functions

rewind: Reset File Pointer to Beginning

rewind is a macro for fseek(fp, 0, SEEK_SET), quickly resetting the file pointer to the start:

void rewind(FILE* stream);

Example:

FILE* fp = fopen("data.txt", "r");
// ... (read some data)
rewind(fp);  // Pointer back to start, can reread

fgetpos/fsetpos: Save/Restore File Position

Used for large files or when saving file position is needed (supports fpos_t type):

int fgetpos(FILE* stream, fpos_t* pos);  // Save current position to pos
int fsetpos(FILE* stream, const fpos_t* pos);  // Restore position from pos

Example:

FILE* fp = fopen("large_file.bin", "rb");
fpos_t pos;

// Save current position
fgetpos(fp, &pos);

// Move to end of file
fseek(fp, 0, SEEK_END);

// Restore previous position
fsetpos(fp, &pos);

File Upload and Download (Network Programming Basics)

File upload and download involve network programming and require the Socket API. The C standard library does not directly support network operations, but they can be implemented using system-provided Socket interfaces. Below is a simple file transfer example based on the TCP protocol.

Network Programming Basic Concepts

  • Socket: Endpoint for network communication, created with socket().
  • IP Address: Identifies a host on the network (e.g., 192.168.1.1).
  • Port Number: Identifies a service on the host (e.g., HTTP default port 80).
  • TCP: Connection-oriented reliable transmission protocol (suitable for file transfer).

File Upload Example (Client to Server)

Server-Side Code (Receive File)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    FILE* fp;

    // Create Socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // Set Socket options (allow port reuse)
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // Bind port
    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // Listen for connections
    if (listen(server_fd, 3) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // Accept client connection
    if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept failed");
        exit(EXIT_FAILURE);
    }

    // Receive filename
    char filename[256];
    recv(new_socket, filename, sizeof(filename), 0);
    printf("Receiving file: %s\n", filename);

    // Open file (write mode)
    fp = fopen(filename, "wb");
    if (fp == NULL) {
        perror("Failed to open file");
        close(new_socket);
        exit(EXIT_FAILURE);
    }

    // Receive file data
    char buffer[BUFFER_SIZE];
    ssize_t bytes_received;
    while ((bytes_received = recv(new_socket, buffer, BUFFER_SIZE, 0)) > 0) {
        fwrite(buffer, 1, bytes_received, fp);  // Write to file
    }

    if (bytes_received < 0) {
        perror("Failed to receive data");
    } else {
        printf("File reception completed\n");
    }

    // Close resources
    fclose(fp);
    close(new_socket);
    close(server_fd);
    return 0;
}

Client-Side Code (Upload File)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    FILE* fp;
    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    // Create Socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert IP address (assuming server IP is 127.0.0.1)
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        perror("Invalid IP address");
        exit(EXIT_FAILURE);
    }

    // Connect to server
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection failed");
        exit(EXIT_FAILURE);
    }

    // Send filename
    char filename[] = "test.txt";
    send(sock, filename, strlen(filename), 0);

    // Open file (read mode)
    fp = fopen(filename, "rb");
    if (fp == NULL) {
        perror("Failed to open file");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // Send file data
    while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
        send(sock, buffer, bytes_read, 0);  // Send data
    }

    if (bytes_read < 0) {
        perror("Failed to read data");
    } else {
        printf("File upload completed\n");
    }

    // Close resources
    fclose(fp);
    close(sock);
    return 0;
}

Compile and Run

  1. Compile server: gcc server.c -o server
  2. Compile client: gcc client.c -o client
  3. Run server: ./server
  4. Run client: ./client

File Download Example (Server to Client)

File download logic is similar to upload, just reverse the read/write direction on client and server:

  • Server reads file data and sends via send.
  • Client receives data via recv and writes to local file.

Summary and Best Practices

Key Summary

  • File Open/Close: Always check fopen return value to avoid resource leaks.
  • File Read/Write: Use fread/fwrite for binary files, fscanf/fprintf for text files; match with appropriate mode.
  • File Positioning: fseek/ftell for random access, more reliable in binary mode.
  • Network File Transfer: Based on TCP protocol, use Socket API for client-server communication.

Best Practices

  • Error Handling: Check return values after all file and network operations, handle errors promptly (e.g., perror).
  • Resource Release: Ensure files (fclose) and sockets (close) are closed to avoid resource leaks.
  • Buffer Management: Use fflush to flush output buffer to prevent data loss.
  • Large File Support: On 64-bit systems, use fseeko/ftello with off_t type to handle files over 2GB.

By mastering these skills, developers can efficiently implement file operations and network file transfer in C, meeting various persistent data management needs.

Share your love