File I/O functions in C
Differences between fread and fgets
During my office hour, several students asked me, since both fread()
and fgets()
can be used to read files, what is the difference between them?
My short answer is that the intent is different, what type of data do you expect to read in? For regular text, use fgets()
; for binary or unspecified, use fread()
.1
However, what exactly is the difference between them? Let’s dive into it together.
Different call parameters
char *fgets(char *s, int size, FILE *stream);
The way to use fgets()
is really simple, just tell it where I want the characters to be stored (s
), the length I want them to be read (size
)2, and where I want them to be retrieved from (stream
).
size_t fread(const void *ptr, size_t size, size_t n, FILE *stream);
For fread()
, in addition to ptr
and stream
, which correspond to the s
and stream
in fgets()
respectively, we need to provide two more parameters: the length of each element, size
, and the total number of elements we want to read, n
.3
Different return types
fgets()
will simply return s
on success, and NULL
on error or unexpected situations.
However, fread()
return the number of items read. It is always smaller or equal to the third parameter n
and equals the number of bytes being read only when the second parameter size
is 1.
Here is an example:
char buf[8];
int r1 = fread(buf, sizeof(char), 8, fp);
int r2 = fread(buf, sizeof(int), 2, fp);
In case of success, those two lines will both read in 8 bytes (1 * 8 = 4 * 2 = 8) from the file fp
and filled up the whole buffer. However, return value for the first line r1
will be 8, since 8 1-byte elements have been transferred; while return value for the second line r2
will be 2, since only 2 4-byte elements have been transferred in this case.
Different reading lengths
Since fgets()
is looking for regular text and guarantees to null-terminate the target buffer, but fread()
only responsible for reading in arbitrary data, there is a 1-byte different for there reading length.
Take the following as an example:
char buf[8];
fgets(buf, sizeof(buf), fp);
fread(buf, 1, sizeof(buf), fp);
Although it is undoubtedly true that buf
has a length of 8 bytes. But when we use fgets()
, it reads at most 7 bytes from file fp
and writes those 7 bytes to buf
along with '\0'
. Whereas fread()
will literally read 8 bytes from the fp
file.
Different unblock conditions
We all know that asking the system to read something may block the program until some trigger conditions are met. The unblock scenario for these two functions is as follows.
For fgets()
:
- Encounter
'\n'
- Fill up buffer for length (n - 1)
- Error
- End of file (EOF)
For fread()
:
- Fill up buffer for length (n)
- Error
- End of file (EOF)
Use Case Demonstration
We can write some simple programs to compare these two functions.
For fgets()
we have the following C program:
char *buf = malloc(maxlen);
FILE *fp = fopen(filename, "rb");
while (fgets(buf, maxlen, fp) != NULL) {
display_for_one_round(buf, strlen(buf) + 1);
}
For fread()
we have:
int length;
char *buf = malloc(maxlen);
FILE *fp = fopen(filename, "rb");
while (length = fread(buf, 1, maxlen, fp)) {
display_for_one_round(buf, length);
}
And we also have two files, in test1.txt
we have regular characters (contains \n
) inside, while in test2.bin
we have some characters (contains \n
) and other random characters/symbols there.
The testing results are as follows:
And
The io_explore
executable and two testing files are located at ~ax2155/public/io_explore/
directory on the server, you are free to check it out and try by yourself.4