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.
r
Readw
Writer+
Reading and writingw+
Reading and writing. The file is created if not founda
Writing and starts at the end of the file. The file is created if not founda+
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.
Top comments (0)