Reading and Writing Files in C
Tags: c, io, file
Published: 2019-01-06 04:38:41

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:

```cpp
#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.

```cpp
#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:

```cpp
~/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 **E**nd **O**f **F**ile 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:

```cpp
#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:

```cpp
#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:

```cpp
~/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.