Previous Page
Next Page

13.5. Random File Access

Random file access refers to the ability to read or modify information directly at any given position in a file. You do this by getting and setting a file position indicator, which represents the current access position in the file associated with a given stream.

13.5.1. Obtaining the Current File Position

The following functions return the current file access position. Use one of these functions when you need to note a position in the file to return to it later.


long ftell( FILE * fp );

ftell( ) returns the file position of the stream specified by fp. For a binary stream, this is the same as the number of characters in the file before this given positionthat is, the offset of the current character from the beginning of the file. ftell( ) returns -1 if an error occurs.


int fgetpos( FILE * restrict fp, fpos_t * restrict ppos );

fgetpos( ) writes the file position indicator for the stream designated by fp to an object of type fpos_t, addressed by ppos. If fp is a wide-oriented stream, then the indicator saved by fgetpos( ) also includes the stream's current conversion state (see "Byte-Oriented and Wide-Oriented Streams," earlier in this chapter). fgetpos( ) returns a nonzero value to indicate that an error occurred. A return value of zero indicates success.

The following example records the positions of all lines in the text file message.txt that begin with the character #:

    #define ARRAY_LEN 1000
    long arrPos[ARRAY_LEN] = { 0L };
    FILE *fp = fopen( "messages.txt", "r" );
    if ( fp != NULL)
    {
      int i = 0, c1 = '\n', c2;
      while ( i < ARRAY_LEN  && ( c2 = getc(fp) ) != EOF )
      {
        if ( c1 == '\n'  &&  c2 == '#' )
          arrPos[i++] = ftell( fp ) - 1;
        c1 = c2;
      }
      /* ... */
    }

13.5.2. Setting the File Access Position

The following functions modify the file position indicator:


int fsetpos( FILE * fp, const fpos_t * ppos );

Sets both the file position indicator and the conversion state to the values stored in the object referenced by ppos. These values must have been obtained by a call to the fgetpos( ) function. If successful, fsetpos( ) returns 0 and clears the stream's EOF flag. A nonzero return value indicates an error.


int fseek( FILE * fp, long offset, int origin );

Sets the file position indicator to a position specified by the value of offset and by a reference point indicated by the origin argument. The offset argument indicates a position relative to one of three possible reference points, which are identified by macro values. Table 13-5 lists these macros, as well as the numeric values that were used for origin before ANSI C defined them. The value of offset can be negative. The resulting file position must be greater than or equal to zero, however.

Table 13-5. The origin parameter in fseek( )

Macro name

Traditional value of origin

Offset is relative to

SEEK_SET

0

The beginning of the file

SEEK_CUR

1

The current file position

SEEK_END

2

The end of the file


When working with text streamson systems that distinguish between text and binary streamsyou should always use a value obtained by calling the ftell( ) function for the offset argument, and let origin have the value SEEK_SET. The function pairs ftell( )--fseek( ) and fgetpos( )--fsetpos( ) are not mutually compatible, because the fpos_t object used by fgetpos( ) and fsetpos( ) to indicate that a file position may not have an arithmetic type.

If successful, fseek( ) clears the stream's EOF flag and returns zero. A nonzero return value indicates an error.

    void rewind( FILE *fp );

rewind( ) sets the file position indicator to the beginning of the file and clears the stream's EOF and error flags. Except for the error flag, the call rewind(fp) is equivalent to:

    (void)fseek( fp, 0L, SEEK_SET )

If the file has been opened for reading and writing, you can perform either a read or a write operation after a successful call to fseek( ), fsetpos( ), or rewind( ).

The following example uses an index table to store the positions of records in the file. This approach permits direct access to a record that needs to be updated.

    // setNewName( ): Finds a keyword in an index table
    // and updates the corresponding record in the file.
    // The file containing the records must be opened in
    // "update mode"; i.e., with the mode string "r+b".
    // Arguments: - A FILE pointer to the open data file;
    //            - The key;
    //            - The new name.
    // Return value: A pointer to the updated record,
    //               or NULL if no such record was found.
    // ---------------------------------------------------------------
    #include <stdio.h>
    #include <string.h>
    #include "Record.h"       // Defines the types Record_t, IndexEntry_t:
                              // typedef struct { long key; char name[32];
                              //                  /* ... */ } Record_t;
                              // typedef struct { long key, pos; } IndexEntry_t;

    extern IndexEntry_t indexTab[ ];      // The index table.
    extern int indexLen;                 // The number of table entries.

    Record_t *setNewName( FILE *fp, long key, const char *newname )
    {
      static Record_t record;
      int i;
      for ( i = 0; i < indexLen; ++i )
      {
        if ( key == indexTab[i].key )
          break;                         // Found the specified key.
      }
      if ( i == indexLen )
        return NULL;                     // No match found.
      // Set the file position to the record:
      if ( fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 )
        return NULL;                     // Positioning failed.

      if ( fread( &record, sizeof(Record_t), 1, fp ) != 1 )  // Read the record.
        return NULL;                     // Error on reading.

      if ( key != record.key )           // Test the key.
        return NULL;
      else
      {                                  // Update the record:
        size_t size = sizeof(record.name);
        strncpy( record.name, newname, size-1 );
        record.name[size-1] = '\0';

        if ( fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 )
          return NULL;                   // Error setting file position.
        if ( fwrite( &record, sizeof(Record_t), 1, fp ) != 1 )
          return NULL;                   // Error writing to file.

        return &record;
      }
    }

The second fseek( ) call before the write operation could also be replaced with the following, moving the file pointer relative to its previous position:

      if ( fseek( fp, -(long)sizeof(Record_t), SEEK_CUR ) != 0 )
        return NULL;                     // Error setting file position.


Previous Page
Next Page