tags:

views:

104

answers:

6

I am trying to learn C and the book I am using (Apress' 'Learn C') has a chapter that is terribly confusing on Random Access functions. The following code is confusing me:

 int    GetNumberOfDinos( void ) {
    FILE    *fp;
    long    fileLength;

    if ( (fp = fopen( kDinoFileName, "r" )) == NULL )
        DoError( "Couldn't open file...Goodbye!" );

    if ( fseek( fp, 0L, SEEK_END ) != 0 )
        DoError( "Couldn't seek to end of file...Goodbye!" );

    if ( (fileLength = ftell( fp )) == -1L )
        DoError( "ftell() failed...Goodbye!" );

    fclose( fp );

    return( (int)(fileLength / kDinoRecordSize) );
}

I understand the purpose of the code, but not how that purpose is being achieved. The fopen line is easy to understand. The fseek and ftell is where my troubles begin. The parameters for fseek are the file, the offset, and then one of the 3 SEEKs. Why is the condition of it not being zero given there? If the file truly does exist (kDinoFileName), and they want to point to the end of that file, why would the location be zero? The file exists and there is information! And then I completely don't understand how the ftell function would ever end up with -1L?? Is this code more difficult than it needs to be?

+5  A: 

The code is simply checking for errors. fseek() returns 0 on success and non-zero on failure. ftell() returns -1 on failure.

As far as "why would the location be zero?" - the fseek() call is requesting to seek from the end of the file (SEEK_END). Zero bytes from the end of the file is.. the end of the file. So

fseek( fp, 0L, SEEK_END )

is a request to move the file pointer to the end of the file.

The code is performing the following steps:

  1. open the file
  2. seek to the end
  3. get the position of the file pointer (which, since we're at the end tells you how many bytes are in the file - note, strictly speaking the file needs to be opened with binary access, for example using "rb", for this to be true)

The code is complicated somewhat by the error handling. Many books and articles leave out the error handling for exactly that reason. However, that has it's own drawback of teaching people to ignore error conditions.

Michael Burr
+2  A: 

-1 can happen if there's some unknown error, like an I/O error trying to read the file. You'll find that C uses this -1 special value since it doesn't have a true exception mechanism like more modern languages do, so this is just a way to say, "hey, something bad happened here.".

Refer here for more info on ftell.

"If an error occurs, -1L is returned, and the global variable errno is set to a positive value. This value can be interpreted by perror."

dcp
It's important to note that if an fseek fails (e.g. because of a read error on the disk media) there's no guarantee as to what a succeeding ftell() will be return. It could be a number which is plausible but wrong. Failure to detect the error on fseek() could cause program to use an incorrect file-size value without any clue that it's bogus.
supercat
@supercat - But if you look at the code the OP posted, he's checking the return value of fseek before he calls ftell, so I'm not sure why your comment "is important to note". If fseek fails, the call to ftell will never happen in his code, so it's really a moot point. I assume here that his DoError function is doing some sort of system.exit call, or terminating the program some other way.
dcp
A: 

Google is your friend:

http://www.cplusplus.com/reference/clibrary/cstdio/fseek/

http://www.cplusplus.com/reference/clibrary/cstdio/ftell/

I/O functions have good error potential, so it's a good practice to check the return values.

Yes, and there's also the POSIX syntax, that requires that fseek() returns the new position instead of error code.

ruslik
+1  A: 

Those conditions are there just as a safeguard in case something bad happens. Even if you know that the file exists and all parameters are right, something could go wrong with the disk, for example. It's just good practice to always check for error conditions so that you don't blindly continue only to figure out much later that something has gone wrong.

casablanca
A: 

fseek returns only a success or failure indication, it does not return the position that the file pointer ended up at. It returns 0 on success, which is the standard success return for many library functions.

In the case of ftell it will return the current file position of the file pointer if it doesn't have an error, so a simple return of 0 on success and non-zero on failure doesn't work. So the ftell function returns -1 when a failure happens. This is the standard error return for functions which return a number that has a meaning.

If you look, the code complains about an error of some kind when an error condition happens according to the information I just gave you.

Omnifarious
A: 

Return values for standard C functions can be strange at times, because many of the functions return an int that has significance, and so it cannot use the same int return value to indicate an error.

In the case of ftell, it returns the current offset of your file pointer. If your file pointer is at the beginning of the file then it is at offset 0. Therefore 0 is a valid offset and cannot be used to indicate an error. So they use -1 instead.

fseek is an instruction. It could return void, but they decide to return something more meaningful. By using 0 for success and non-zero for failure, they can return different non-zero values to indicate different errors.

Incidentally the code is calculating the size of the file by going to the end then asking for the current location. You will sometimes do this if you want to load the file into memory and need to know how many bytes to allocate.

CashCow