Reading and Writing Raw Data

From your early C language training, you should know the difference between 1088 as a string and 1088 as an integer. They may look the same to human eyes, but inside the computer they’re completely different.

The string 1088 is composed of five character values: 1, 0, 8, 8, and the null character \0 to terminate the string.

The integer value 1088 is stored as a bit pattern in two or more bytes.

The good news is that you can code with either implementation of 1088, converting between string and integer as you go. For file I/O, 1088 must be written and read as a string or written and read as raw data for the process to the successful. You can’t switch between the two. This rule works out well because different file I/O functions are used to write strings and raw data.

For writing and reading raw-data, two file I/O functions are fwrite() and fread(). These both work with a file handle returned from a successful fopen() function. Both functions are prototyped in the stdio.h header file and both have similar arguments:

size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
size_t fwrite(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

void *restric ptr is fancy talk for the address of the item to be written to a file. If the item isn’t a pointer, affix the & (address-of) operator to the variable’s name.

size_t size is the size of the item written or read, as expressed in bytes. For example, if you’re writing an integer value, you would put the operator sizeof(int) to get the proper size value.

size_t nitems is the number of items to write. So if you’re writing an array of 4 double values, the nitems argument would be 4.

FILE *restict stream is the pointer variable returned from a successful fopen() statement.

The value returned is the number of bytes written to or read from the file.

The following code is similar to the example from last week’s Lesson, though integer value 1088 is written and read instead of the string 1088:

#include <stdio.h>

int main()
{
    const char filename[] = "highscore.dat";
    int score = 1088;
    FILE *hs;

    /* create the file and write the value */
    printf("Writing high score: %d\n",score);
    hs = fopen(filename,"w");
    if( hs == NULL)
    {
        fprintf(stderr,"Error writing to %s\n",filename);
        return(1);
    }
    fwrite(&score, sizeof(int), 1, hs);
    fclose(hs);

    /* open the file and read the value */
    hs = fopen(filename,"r");
    if( hs == NULL)
    {
        fprintf(stderr,"Error reading from  %s\n",filename);
        return(1);
    }
    fread(&score, sizeof(int), 1, hs);
    printf("Reading high score: %d\n",score);
    fclose(hs);

    return(0);
}

Only Lines 17 and 27 are different between this code and the example shown last week. These are the fwrite() and fread() functions. But the data written to the file is completely different, despite the output being exactly the same:

Writing high score: 1088
Reading high score: 1088

Here’s the file’s raw data, courtesy of the hexdump utility:

00000000  40 04 00 00                                       |@...|
00000004

The four raw bytes (the size of an int) appear in the file, not the characters 1, 0, 8, and 8. And the data was read back into the score variable cleanly, without the need to convert between a string and int.

In next week’s Lesson, I begin to explore random file access.

Leave a Reply