Reading and Writing Structures to a File

You need not read all structures from a file when you know the exact one you want. To fetch that record, you use the fseek() function. This function manipulates the file position indicator, allowing for random access to a file’s data.

From last week’s Lesson, I setup code that wrote five person structures to a file, family.data. Run the code from that lesson to create the file that’s used for this Lesson.

Knowing that the file family.data contains five person structures, I can use the fseek() function to set the file position indicator and read a specific record (structure) stored in the file. Here’s how the fseek() function is defined:

int fseek(FILE *stream, long offset, int whence);

*stream is the handle of an open file. It can be opened for reading, writing, or both.

offset is the location where the file position indicator is set.

whence references where the offset argument is measured from; it’s not always the start of the file. Constants SEEK_SET, SEEK_CUR, and SEEK_END represent the start of the file, current file position indicator location, and the end of the file, respectively.

Click here to view sample code that fetches a specific record from the family.data file. The key to the code is the fseek() statement at Line 30:

fseek(fp, (record-1)*sizeof(struct person), SEEK_SET);

Variable fp is the FILE pointer. record is the structure to be read (input as values 1 through 5). The sizeof operator is used on struct person to fetch its size in bytes, which is how the offset is calculated. Constant SEEK_SET directs the function to look from the start of the file.

After setting the file position indicator, an fread() function fetches the structure from the file. The code displays the results. Here’s a sample run:

Fetch which record (1 to 5)? 4
Record 4:
Name: Missy
Age: 12
Weight: 80.2

Say that Missy had a birthday and she’s now 13. To update her record in the file, you first read in her record. In memory, the record is modified, updating the age member of the person structure. Then that record is written back to the same position in the file. The file position pointer must be set to initially read the specific record and set again to write it back to the same location within the file.

Click here to view the code that updates a record in the file. Constant RECORD is set to the record to fetch, 3, which is Missy’s structure array element. Here’s the fseek() statement, which repeats three times in the code:

fseek(fp, RECORD*sizeof(struct person), SEEK_SET);

The first time is to position the record to be read. The second time is to position the updated record to be written. And the final time is to read back in the record to confirm that the change was made.

Here is the output:

Record 4:
Name: Missy
Age: 12
Weight: 80.2
Record updated:
Record 4:
Name: Missy
Age: 13
Weight: 80.2

Random file access allows you to fetch and put portions of a file, which is far more efficient than processing the entire file, manipulating it in memory, then writing it back out. To perform random file access, you need not open the file in any special manner; just use the fseek() function to set the file position indicator, and you’re doing random file access.

One issue that arises with structures and files is pointers. When the structure is a pointer or contains pointers, you must ensure that you write the data to the file and not the pointer’s address. I address this issue in next week’s Lesson.

3 thoughts on “Reading and Writing Structures to a File

  1. I’ve never done this, most of the data persistence I have ever done has used relational databases and the rest XML or JSON. (Oh, and MongoDB which I try to forget about!)

    Obviously the approach you describe here is more compact but I would be worried about saving stuff to a file that is C-specific. Do you know if the files created here are readable in other languages without too much hassle?

  2. Great question! I would assume that as long as the format is documented, the file could be read in any language. Then again, I’m not that intimate with raw data reads in other languages. Still, I believe the reason XML, JSON, and others were developed was to allow for more flexibility in data storage than C’s rigid structures.

    Back in the day, I would read all sort of file data using this technique. I had a book (maybe I still do) that listed all the then-popular file formats. As long as you could craft a structure in C that matched the way the file stored data, you could read it. Especially for low-level network packets, which are all fairly consistent, using a structure was how they’re manipulated.

  3. The advantages of XML and JSON are that they are not just data formats but data PLUS metadata formats so you can more or less reconstruct a data structure without having existing knowledge of it. The downside is obviously a lot of excess baggage. XML files in particular can have more metadata than data.

    Any file is ultimately a row of bits and can be read by any language but programmers in higher-level languages would be unhappy if they had to craft individual code to convert that row of bits into a data structure for each separate C struct it was created from.

    It shouldn’t be too difficult to write a module or class in, say, Python which took a filestream, byte array or whatever and a specification of the underlying data structure and converted the one into the other.

Leave a Reply