DEV Community

Noah11012
Noah11012

Posted on

Reading and Writing Files in C

If you've ever used a higher level language's file I/O capabilities like that of Python or Rust, you may have thought to yourself how easy it is to manipulate files to solve your problems albeit to read in a configuration file, parse a file into tokens, or simply writing information to disk. Maybe you've wondered how file I/O works in C?*

*Of course, nobody is a mind reader including me.

Reading a Character

The most fundamental file I/O starts with reading/writing characters. The C standard I/O library provides the functions fgetc() and fputc() for reading and writing characters, respectively.

How they work is very simple. Both operate with file streams, or more commonly known in C, the FILE type. This type is a structure that keeps track of numerous different states such as how many characters were read/written, the error state, and more. We never view or change the information held within FILE directly, instead, we use functions given to us to retrieve this information. All C standard I/O functions that deal with files reflect this. They all take a pointer to a FILE.

With the introductory text out of the way, we can start using files in C.

First, we need to obtain a FILE object to use for our file adventures. How do we get one? With the fopen() function.

fopen() is straightforward: the first argument is the path to the file and the second is the mode.

Mode? What's that?

A mode in file I/O contexts refer to what permissions we have to the file and how it is opened. Permissions tell if we can read, write, or both. How the file is opened might mean different things depending on the permissions.

In fopen() the mode is a string.

  1. r
    Read

  2. w
    Write

  3. r+
    Reading and writing

  4. w+
    Reading and writing. The file is created if not found

  5. a
    Writing and starts at the end of the file. The file is created if not found

  6. a+
    Reading and writing. The file is created if not found. The initial position for reading starts at the beginning but appends to the file when writing

On error, fopen() returns a null pointer or FILE * on success.

Example:

#include <stdio.h>

int main(void)
{
    FILE *file = fopen("test.txt", "w");
    if(file == NULL)
    {
        // handle error
    }

    // use file
}

For starters, let say we want to write a message into a file only using fputc(). The first thing we need obviously is our message which is a string. This can just be a string literal for our purposes.

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

int main(void)
{
    FILE *file = fopen("message.txt", "w");
    if(file == NULL)
    {
        printf("Failed to open file!\n");  
        return 1;
    }

    char *message = "I am a file\n";
    int i = 0;
    while(i < strlen(message))
    {
        fputc(message[i], file);
        i++;
    }
}

Output:

~/Desktop 
 clang main.c

~/Desktop 
 ./a.out

~/Desktop 
 cat message.txt 
I am a file

~/Desktop 
 

This is perfect for demonstration purposes but in the real world, something might fail internally and your character may go unwritten. fputc() and friends return the character written if successful, but EOF on error. EOF is a macro that stands for End Of File and expands out to a constant integer expression. Typically, it is returned by functions reading in characters to let the calling code know that the last character has been read. In others like fputc() it means an error has occurred.

Updated version with error checking:

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

int main(void)
{
    FILE *file = fopen("message.txt", "w");
    if(file == NULL)
    {
        printf("Failed to open file!\n");
        return 1;
    }

    char *message = "I am a file\n";
    int i = 0;
    while(i < strlen(message))
    {
        int result = fputc(message[i], file);
        if(result == EOF)
        {
            printf("Failed to write a character!\n");
            return 1;
        }
        i++;
    }
}

Much better. If something went wrong, then we will know about it once it has happened.

Next, what if we are trying to read from the same file? The code would mostly be the same except the mode would be set to reading and the while loop is now reading character by character from the file.

Example:

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

int main(void)
{
    FILE *file = fopen("message.txt", "r");
    if(file == NULL)
    {
        printf("Failed to open file!\n");
        return 1;
    }

    char message[100];
    int c = 0;
    int i = 0;
    while((c = fgetc(file)) != EOF)
    {
        message[i] = c;
        i++;
    }

    printf("%s", message);
}

Output:

~/Desktop 
 clang main.c 

~/Desktop 
 ./a.out 
I am a file

~/Desktop 
 

Next

In the next post, we'll talk about the other functions available to us for file I/O.

Discussion (0)