Enabling Random Memory-File Access

The two types of file access are sequential and random. Sequential access means the file’s data is read from beginning to end, one byte after the other. Random access isn’t random in the sense that it’s haphazard. No, random access means you can read data from any position in the file: beginning, middle, or end.

For example, if you know the data chunk you want is at offset 100 bytes from the start of the file, you can position a file pointer (I don’t like using the term “pointer”) at offset 100 and start reading. The fseek() function is responsible for this manipulation.

Continuing with my memory-file implementation in last week’s Lesson, I sought to write a mem_seek() function to parallel fseek(). This function moves a position indicator in the memory-file to enable random memory-file access:

/* move the memory offset index
   input- memory file handle, offset, whence
   output- current offset or -1
   whence:  SEEK_SET (start)
               SEEK_CUR (current)
            SEEK_END (end)
 */
int mem_seek(struct mem_file *m, long position, int whence)
{
    /* return -1 on various errors */
    if( m->address==NULL )
        return(-1);

    /* adjust m->offset value */
    if( whence==SEEK_SET )
    {
        if( position<0 )
            return(-1);
        /* no negative offsets! */
        if( position>m->size )
            return(-1);
        /* set offset from the start */
        m->offset = position;
        return(m->offset);
    }
    else if( whence==SEEK_CUR )
    {
        if( m->offset+position<0 || m->offset+position > m->size )
            return(-1);
        m->offset+=position;
        return(m->offset);
    }
    else if( whence==SEEK_END )
    {
        if( position<0 || m->size-position<0 )
            return(-1);
        m->offset = m->size-position;
        return(m->offset);
    }
    else
        return(-1);
}

The three operations are based on the three defined constants shared with the fseek() function:

SEEK_SET to move the pointer offset from the memory-file’s start
SEEK_CUR to move the pointer offset from the current position
SEEK_END to move the pointer offset from the end of the memory-file

The offset values for SEEK_SET and SEEK_END must be positive. These are offsets from the start and end of the file and calculated that way in the code.

For the SEEK_CUR argument the position change can be positive or negative. Math in the code works to prevent an overflow, where the position indicator leaps below zero or beyond the end of the file, m->size. I’m unsure how the fseek() function is coded, but these are the rules I implemented.

To test my mem_seek() function, I modified the main() function to write the 26 letters of the alphabet to a memory file. These letters are then read back using specific options in the mem_seek() function:

/* test the memory file functions */
int main()
{
    static char name1[] = "memory file";
    struct mem_file *mfp1;
    int a,r;

    mfp1 = mem_open(name1);
    if( mfp1==NULL )
    {
        fprintf(stderr,"Unable to create %s\n",name1);
        exit(1);
    }
    printf("Memory file '%s' created\n",name1);

    /* write the alphabet to the memory-file */
    for( a='A'; a<='Z'; a++ )
        mem_putc(a,mfp1);

    /* read back in the entire alphabet */
    printf("Reading the entire file: ");
    r = mem_seek(mfp1,0,SEEK_SET);
    if( r==-1)
    {
        fprintf(stderr,"Position error\n");
        exit(1);
    }
    for( a=0; a<26; a++ )
        putchar( mem_getc(mfp1) );
    putchar('\n');

    /* read the last ten characters */
    printf("Reading the last ten characters: ");
    r = mem_seek(mfp1,10,SEEK_END);
    if( r==-1)
    {
        fprintf(stderr,"Position error\n");
        exit(1);
    }
    for( a=0; a<10; a++ )
        putchar( mem_getc(mfp1) );
    putchar('\n');

    /* read the middle ten characters */
    printf("Reading the middle ten characters: ");
        /* position is at the end of the file */
    r = mem_seek(mfp1,-18,SEEK_CUR);
    if( r==-1)
    {
        fprintf(stderr,"Position error\n");
        exit(1);
    }
    for( a=0; a<10; a++ )
        putchar( mem_getc(mfp1) );
    putchar('\n');

    mem_close(mfp1);
    printf("Memory file '%s' closed\n",name1);

    return(0);
}

The bulk of the main() function deals with error-checking, to ensure that when I’m moving the file position pointer that I’m not accessing unallocated memory. The program reads all characters in the memory-file, then the last ten, finally the middle ten. Here is a sample run:

Memory file 'memory file' created
Reading the entire file: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Reading the last ten characters: QRSTUVWXYZ
Reading the middle ten characters: IJKLMNOPQR
Memory file 'memory file' closed

The full code is 205 lines long, which is too much to list in this post. Click here to view the full code on Github.

For next week’s Lesson, I present how to split up this code into multiple modules for easier implementation of new features.

Leave a Reply