I've a rather curious question, not very practical at all really. The error (reading a binary file in r
mode) is in plain sight but I'm confused by something else.
Here's the code-
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdint.h>
#define BUFFER_LEN 512
typedef uint8_t BYTE;
int main()
{
FILE* memcard = fopen("card.raw", "r");
BYTE buffer[BUFFER_LEN];
int count = 0;
while (fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard) != 0)
{
printf("count: %d\n", count++);
}
fclose(memcard);
return 0;
}
Now, card.raw
is a binary file, so this reading will go wrong due to being read in r
mode instead of rb
. But what I'm curious about is that, that loop executes exactly 3 times, in the final execution, it doesn't even read 512 bytes.
Now if I change that loop to
while (fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard) != 0)
{
printf("ftell: %ld\n", ftell(memcard));
}
It no longer stops at 3 executions. In fact, it keeps going until (presumabely) the end of file. The fread
count is still messed up. Many of the reads do not return 512 as elements read. But that is most probably due to the file being opened in r
mode and all the encoding errors it's being accompanied with .
ftell
shouldn't affect the file itself, then why does including ftell
in the loop make it execute more times?
I decided to change the loop a bit more to extract more info-
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
printf("fread bytes read: %d\n", count);
printf("ftell: %ld\n", ftell(memcard));
}
This loops just as many times as it would, provided ftell
is included in the loop and the first few results look like-
Now if I just remove that ftell
line completely, it gives me-
Only 3 executions, yet nothing changed.
What's the explanation behind this behaviour?
Note: I know the counts returned by both fread
and ftell
are probably wrong due to the read mode, that's not my concern though. I'm only curious - why the difference, between including ftell
and not including it.
Also, in case it helps, The card.raw
file is actually just the cs50 pset4 "memory card". You can get it by wget https://cdn.cs50.net/2019/fall/psets/4/recover/recover.zip
and storing the output file in a .zip
Edit: I should mention this was on windows and using clang tools for VS2019. The command line options (checked from VS2019 project properties) looked like-
/permissive- /GS /W3 "Debug\" "Debug\" /Zi /Od "Debug\vc142.pdb" /fp:precise /D "_CRT_SECURE_NO_WARNINGS" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /WX- /Gd /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\Test.pch" /diagnostics:column
Edit: Also, I did check for ferror
inside the loop, with and without ftell
, got no errors from it at all. In fact, feof
returns 1 after the loop, in both cases.
Edit: I also tried adding a memcard == NULL
check right after the fopen
, same behaviour.
Edit: To address the answer by @orlp. I did, infact, check for errors. I should definitely have posted it though.
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
if ((err = ferror(memcard)))
{
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
printf("fread bytes read: %d\n", count);
printf("ftell: %ld\n", ftell(memcard));
}
if ((err = ferror(memcard)))
{
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
Neither of the 2 if
statements are ever triggered.
Edit: I thought we got the answer already, it was ftell
resetting the EOF. But I changed the loop to-
while ((count = fread(buffer, sizeof(*buffer), BUFFER_LEN, memcard)) != 0)
{
if ((err = ferror(memcard)))
{
fclose(memcard);
fprintf(stderr, "Error code: %d", err);
perror("Error: ");
return 1;
}
if (feof(memcard))
{
printf("reached before\n");
}
printf("fread bytes read: %d\n", count);
ftell(memcard);
if (feof(memcard))
{
printf("reached after\n");
}
}
this triggers both the first if(feof)
and the second if(feof)
As expected though, if I change the ftell
to fseek(memcard, 0, SEEK_CUR)
, the EOF
is reset and the reached after
is never printed.
Hence the "rb" mode opens the file in binary format for reading, while the "wb" mode opens the file in binary format for writing. Unlike text files, binary files are not human-readable. When opened using any text editor, the data is unrecognizable.
r+ is used for reading, and writing mode. b is for binary. r+b mode is open the binary file in read or write mode.
Open a binary file in append mode for writing at the end of the file. The fopen function creates the file if it does not exist. r+b or rb+ Open a binary file for both reading and writing.
Binary files must be read by programs to be useable. Sometimes, the data generated by other programs must be processed by R as a binary file. R is therefore required to create binary files that can be shared with other programs. There are two methods to read and write binary files in R: WriteBin() and ReadBin() .
As some commentors pointed out, it ran into an EOF
, and ftell
actually got rid of that EOF. Why? To find the answer, we have to look inside glibc's source code. We can find the source for ftell
::
long int
_IO_ftell (FILE *fp)
{
off64_t pos;
CHECK_FILE (fp, -1L);
_IO_acquire_lock (fp);
pos = _IO_seekoff_unlocked (fp, 0, _IO_seek_cur, 0);
if (_IO_in_backup (fp) && pos != _IO_pos_BAD)
{
if (_IO_vtable_offset (fp) != 0 || fp->_mode <= 0)
pos -= fp->_IO_save_end - fp->_IO_save_base;
}
_IO_release_lock (fp);
if (pos == _IO_pos_BAD)
{
if (errno == 0)
__set_errno (EIO);
return -1L;
}
if ((off64_t) (long int) pos != pos)
{
__set_errno (EOVERFLOW);
return -1L;
}
return pos;
}
libc_hidden_def (_IO_ftell)
weak_alias (_IO_ftell, ftell)
This is the important line:
pos = _IO_seekoff_unlocked (fp, 0, _IO_seek_cur, 0);
Let's find the source for _IO_seekoff_unlocked
:
off64_t
_IO_seekoff_unlocked (FILE *fp, off64_t offset, int dir, int mode)
{
if (dir != _IO_seek_cur && dir != _IO_seek_set && dir != _IO_seek_end)
{
__set_errno (EINVAL);
return EOF;
}
/* If we have a backup buffer, get rid of it, since the __seekoff
callback may not know to do the right thing about it.
This may be over-kill, but it'll do for now. TODO */
if (mode != 0 && ((_IO_fwide (fp, 0) < 0 && _IO_have_backup (fp))
|| (_IO_fwide (fp, 0) > 0 && _IO_have_wbackup (fp))))
{
if (dir == _IO_seek_cur && _IO_in_backup (fp))
{
if (_IO_vtable_offset (fp) != 0 || fp->_mode <= 0)
offset -= fp->_IO_read_end - fp->_IO_read_ptr;
else
abort ();
}
if (_IO_fwide (fp, 0) < 0)
_IO_free_backup_area (fp);
else
_IO_free_wbackup_area (fp);
}
return _IO_SEEKOFF (fp, offset, dir, mode);
}
Basically, it just does some checks then calls _IO_SEEKOFF
, so let's find its source:
/* The 'seekoff' hook moves the stream position to a new position
relative to the start of the file (if DIR==0), the current position
(MODE==1), or the end of the file (MODE==2).
It matches the streambuf::seekoff virtual function.
It is also used for the ANSI fseek function. */
typedef off64_t (*_IO_seekoff_t) (FILE *FP, off64_t OFF, int DIR,
int MODE);
#define _IO_SEEKOFF(FP, OFF, DIR, MODE) JUMP3 (__seekoff, FP, OFF, DIR, MODE)
So basically, ftell
ends up calling a function which is the equivalent of fseek(fp, 0, SEEK_CUR)
. And in the fseek
standards we see: "A successful call to the fseek()
function clears the end-of-file indicator for the stream." That's why ftell
changes the behavior of the program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With