Sort an array of structures in a binary file - c

I have a structure that is like:
typedef struct Student {
char name[50];
int roll_no;
char telephone[50];
char address[50];
}
Student;
And an array of such structures,
typedef struct SReg {
Student arr[MAX_STUDENTS];
int curr_length; //curr_length stores number of students currently in the student register, MAX is 100
}
SReg;
I have an SReg variable sr.
The name field contains names in Surname, Firstname format. For example, Rogers, Steve. I want to be able to sort the structures dumped into a binary file by their firstnames. The contents of sr has been exported to the binary file as follows.
FILE *file = fopen(fname, "wb");
if (file == NULL) {
printf("The file cannot be created!");
return 0;
}
if (fwrite(sr, sizeof(Student), sr->curr_length, file) != sr->curr_length) return 0;
fclose(file);
return 1;
}
Now I want to implement bubble sort based on the firstnames of the "name" field of the structure. I can get to the first names of students once I can access the name field of the structure using:
char *ptr_i = NULL;
char *ptr_j = NULL;
// To skip surname and get to first name
ptr_i = strchr(sr->arr[i].name, ' ') + 1;
ptr_j = strchr(sr->arr[j].name, ' ') + 1;
// ptr_i and ptr_j contain the two firstnames to be compared, and compare using strcmp
if (strcmp(ptr_i, ptr_j) > 0) {
Student temp;
temp = sr->arr[i];
sr->arr[i] = sr->arr[j];
sr->arr[j] = temp;
}
But my question is, how do I access the name field of each structure stored in the binary file? I can get the total number of such structures in the file by
fseek(file, 0, SEEK_END);
// get total number of records in file
int total = ftell(file)/sizeof(Student);
rewind(file);
So after that I can use two nested for loops and make them run total number of iterations and get a bubble sort algo. But my question is, how do I actually access the names and compare them? How can I get to the first names? I'm doing this because I need to sort a binary file in place. I've seen that this can probably done using fseek, fwrite and fread, but no clue actually how.

Think of the file as an byte array, and with the following utitlity functions you're able to read and write from and to the file, from and to specific indices.
e.g.
//reads a student from the specified index
Student* getStudent(FILE *f, long offset, Student *res)
{
fseek(f, sizeof(Student) * offset, SEEK_SET); //set file position
fread(res, sizeof(Student), 1, f); //read into res
return res; //return res
}
//writes the student to the specified index
void setStudent(FILE *f, long offset, Student *student)
{
fseek(f, sizeof(Student) * offset, SEEK_SET); //set file position
fwrite(student, sizeof(Student), 1, f); //write student
}
//iterate over all students stored in the file
void foreachStudent(FILE *f, void (*callback)(Student*))
{
fseek(f, 0, SEEK_END); //set pos to end
int num = ftell(f) / sizeof(Student); //num students in file
rewind(f); //set pos to begin
Student student; //temporary object (buffer)
for (int i=0; i < num; ++i) { //for num elements
fread(&student, sizeof(Student), 1, f); //read next element
callback(&student); //invoke callback
}
}
Note: the code above assumes that there are no errors.

Related

Question about offsets and reading a file line by line

#include "offsetFinder.h"
/** Reads a GIS record file (as described in the corresponding project
* specification), and determines, for each GIS record contained in that
* file, the offset at which that record begins. The offsets are stored
* into an array supplied by the caller.
*
* Pre: gisFile is open on a GIS record file
* offsets[] is an array large enough to hold the offsets
* Post: offsets[] contains the GIS record offsets, in the order
* the records occur in the file
* Returns: the number of offsets that were stored in offsets[]
*/
uint32_t findOffsets(FILE* gisFile, uint32_t offsets[]) {
FILE *op;
/*** Complete the implementation of this function ***/
int count = 0;
char offsets[1000];
char *reader;
op = fopen(gisFile, "r");
if (!op) {
perror("Failed to open file!\n");
exit(1);
}
else {
reader = offsets;
while (*reader != '\n' && fgets(offsets, sizeof(offsets), op)) {
count++;
}
}
return count;
}
Hello all, I have a question about this assignment. Is this set up alright?
For the GISData.txt, I am supposed to read through the file and I have to return the number of offsets that were stored in offsets[].
FEATURE_ID|FEATURE_NAME|FEATURE_CLASS|STATE_ALPHA|STATE_NUMERIC|COUNTY_NAME|COUNTY_NUMERIC|PRIMARY_LAT_DMS|PRIM_LONG_DMS|PRIM_LAT_DEC|PRIM_LONG_DEC|SOURCE_LAT_DMS|SOURCE_LONG_DMS|SOURCE_LAT_DEC|SOURCE_LONG_DEC|ELEV_IN_M|ELEV_IN_FT|MAP_NAME|DATE_CREATED|DATE_EDITED
885513|Siegrest Draw|Valley|NM|35|Eddy|015|323815N|1043256W|32.6376116|-104.5488549|323859N|1043732W|32.6498321|-104.6255227|1095|3592|Parish Ranch|11/13/1980|
885526|AAA Tank|Reservoir|NM|35|Eddy|015|321043N|1041456W|32.1786543|-104.2489615|||||1006|3300|Bond Draw|11/13/1980|06/23/2011
885566|Adobe Draw|Valley|NM|35|Eddy|015|322820N|1042141W|32.4723375|-104.361345|322704N|1042129W|32.4511111|-104.3580556|1007|3304|Carlsbad West|11/13/1980|
885567|Adobe Flat|Flat|NM|35|Eddy|015|322849N|1042119W|32.4803932|-104.3552339|||||1006|3300|Carlsbad West|11/13/1980|
885607|Alacran Hills|Range|NM|35|Eddy|015|322812N|1041055W|32.4701183|-104.1818931|||||1009|3310|Carlsbad East|11/13/1980|
885684|Alkali Lake|Lake|NM|35|Eddy|015|323039N|1041133W|32.5109371|-104.1924802|||||966|3169|Angel Draw|11/13/1980|06/23/2011
885697|Allen Well|Well|NM|35|Eddy|015|322309N|1042120W|32.3859489|-104.3555084|||||1038|3405|Carlsbad West|11/13/1980|
This is a snippet of the GISData.txt and each region data (a line) is considered a GIS record.
"The offsets referred to in the assignment are the positions at which the GIS records begin in the GIS data file.
Since each GIS record occupies a whole line, the offset of a GIS record is simply the offset of the first byte in the GIS record.
And, of course, the first line in the GIS data file does not contain a GIS record, so there is no GIS record at offset 0."
Can someone look over my code and revise it if I'm completely wrong? Thank you!!
"For the GISData.txt, I am supposed to read through the file and I have to return the number of offsets that were stored in
offsets[]....Can someone look over my code and revise it if I'm
completely wrong?"
First it appears that there is some confusion about what offset means in this question. And, after Googling "gid offset", I can understand why. Here is a GIS specific definition:
_" offset
[cartography] In cartography, the displacement or movement of features so that they do not overlap when displayed at a given scale.
For example, a road can be offset from a river if the symbols are wide
enough that they overlap.
[symbology] In symbology, the shift of the origin or insertion point of a symbol in an x and/or y direction.
[ESRI software] In ArcGIS, a change in or the act of changing the z-value for a surface or features in a scene by a constant amount or
by using an expression. Offsets may be applied to make features draw
just above a surface."_
And this "GIS System in C" definition:
"The file can be thought of as a sequence of bytes, each at a unique offset from the beginning of the file, just like the cells of an
array. So, each GIS record begins at a unique offset from the
beginning of the file"
These two definitions, although both deriving from searches on gis offset, are so different as to not offer any clarification on what the terms mean in this question. For purposes of this answer then I am taking my queues from your responses in the comments, and will address how to parse the first field in each record of the file. (excluding the header record in line 1.)
Here are some suggested steps to consider that could be used to implement this.
Steps to consider:
Prototype design
As described in comments, the prototype to the findOffsets() function should provide following: filespec, size of array, array. Not mentioned in comments, but also useful might be the length of the longest record that will be read. eg:
uint32_t findOffsets(const char *fileSpec, size_t longestElement, size_t numElements, uint32_t offsets[numElements]);
From calling function
read file once to determine number of records. eg: numRecords. See
int count_names(const char *filename, size_t *count){...}
example here for example of how to read number of records, (and when needed to get longest record.) in file. close file when done:
use number of records from previous step to size the array.
Example:
uint32_t offsets[numRecords-1]; //-1 skipping header line
memset(records, 0, sizeof records);
call findOffsets()
Example:
size_t numOffsets = sizeof records/sizeof *records
uint32_t count = findOffsets("c:\\gis\\data.gis", longestRecord, numOffsets, offsets);
if(count > 0)
{
//do something with records
}
Inside findOffsets()
Open file for second read of process
Read each line of file (skipping header line)
Parse first '|' delimited token from each line
convert parsed token from string to integer
close file
return count of lines processed.
A code example (with very limited safeties/error checking) is below showing how this could be done. It was tested using your sample file contents, and borrows from code linked above, adapted to this purpose:
const char *fileSpec = "C:\\some_directory\\gisData.gis";
uint32_t findOffsets(const char *fileSpec, size_t longestElement, size_t numElements, uint32_t offsets[numElements]);
int count_lines_in_file(const char *filename, size_t *count);
size_t filesize(const char *fn);
int main(void)
{
size_t numRecords = 0;
int longestRecord = count_lines_in_file(fileSpec, &numRecords)+1;//+1 room for null terminator
uint32_t offsets[numRecords -1];//-1 - skipping header line
memset(offsets, 0, sizeof offsets);//initialize VLA offsets
int recordsProcessed = findOffsets(fileSpec, longestRecord, numRecords -1, offsets);//do the work
return 0;
}
uint32_t findOffsets(const char *fileSpec, size_t longestElement, size_t numElements, uint32_t offsets[numElements])
{
char *delim = "|";
char *tok = NULL;
char line[longestElement+1]; //+1 - room for null terminator during read.
memset(line, 0, sizeof line);//initialize VLA line to all zeros
int inx = 0;
FILE *fp = fopen(fileSpec, "r");
if(fp)
{
while(fgets(line, sizeof line, fp))//loop to read all lines in file
{
if(!strstr(line, "FEATURE_ID"))//skip header line, process all other lines
{
tok = strtok(line, delim);//extract first field
if(tok)
{
offsets[inx] = atoi(tok);//convert token and store number
inx++;
}
}
}
fclose(fp);
}
return inx;
}
//passes back count of lines in file, and return longest line
int count_lines_in_file(const char *filename, size_t *count)
{
int len=0, lenKeep = 0;
FILE *fp = fopen(filename, "r");
if(fp)
{
char *tok = NULL;
char *delim = "\n";
int cnt = 0;
size_t fSize = filesize(filename);
char *buf = calloc(fSize, 1);
while(fgets(buf, fSize, fp)) //goes to newline for each get
{
tok = strtok(buf, delim);
while(tok)
{
cnt++;
len = strlen(tok);
if(lenKeep < len) lenKeep = len;
tok = strtok(NULL, delim);
}
}
*count = cnt;
fclose(fp);
free(buf);
}
return lenKeep;
}
//return file size in bytes (binary read)
size_t filesize(const char *fn)
{
size_t size = 0;
FILE*fp = fopen(fn, "rb");
if(fp)
{
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
fclose(fp);
}
return size;
}

Saving unwanted data to an error file

Hi I currently have a piece of code that grabs names from a file and then saves it to another.
However I would now like to save any invalid information to a fixed error file and was wondering how I'd go about it
My code:
Struct:
struct Person{
char fName[16]; //string to store the persons first name
char lName[21]; //string to store the persons last name
};
Main
int main(){
int recordCount = 0; //used to keep track of the number of records currently in memory
struct Person *records;
records = malloc(sizeof(struct Person));
records = open(&recordCount, records);
records = addRecord(&recordCount, records);
save(recordCount, records);
return 0; //End the program and return 0 to the operating system
}
open(&recordCount, records) function:
struct Person* open(int *rCount, struct Person *records){
FILE *recordFile;
char fileName[30] = {'\0'};
int i = *rCount;
char test;
puts("Enter a filename to open :");
scanf("%s", fileName);
if((recordFile = fopen(fileName,"r"))==NULL){
printf("Couldn't open the file: %s\n",fileName);
exit(1);
}
else{
test = fscanf(recordFile,"%s %s", records[i].fName,records[i].lName);
while(test!= EOF){
i++;
records = realloc(records,(i+1)*sizeof(struct Person));
test = fscanf(recordFile,"%s %s", records[i].fName,records[i].lName);
}
fclose(recordFile); // close the file
}
*rCount = i;
return records; //add i (records read from the file) to rCount (the current record count)
}
addRecord(&recordCount, records) function
struct Person* addRecord(int* rCount, struct Person *records){
int valid = 0; //used to indicated valid input
int length = 0; //used to store the string lengths
int i = 0; //used in the for loops
char fNameTest[16]; //temporary storage of input to be checked before adding to records
char lNameTest[21]; //temporary storage of input to be checked before adding to records
//Checking the length of data input for fName
do{
length = strlen(fNameTest);
if(length < 16){
for(i=0;i<=length;i++)
records[*rCount].fName[i] = fNameTest[i]; //if correct insert the record at the index determined by rCount
valid=1;
}
else{
valid = 0;
}
}while(valid!=1);
//Checking the length of data input for lName
do{
length = strlen(lNameTest);
if(length < 21){
for(i=0;i<=length;i++)
records[*rCount].lName[i] = lNameTest[i]; //if correct insert the record at the index determined by rCount
valid=1;
(*rCount)++; //At this point ID,fName and lName have been stored so increment rCount
}
else{
valid = 0;
}
}while(valid!=1);
records = realloc(records,((*rCount)+1)*sizeof(struct Person));
return records; //return rCount as the new updated recordCount
}
save(recordCount, records) function
void save(int rCount, struct Person *records){
FILE *recordFile; //file handle
char fileName[30] = { '\0'}; //string to store the file name
int i;
puts("Enter a filename to save the records :"); //ask the user for the filename
scanf("%s", fileName); //store the filename: data input should be checked
//here in your program
//try and open the file for writing and react accordingly if there is a problem
if((recordFile = fopen(fileName,"w"))==NULL){
printf("Couldn't open the file: %s\n",fileName);
}
else{ //the file opened so print the records array of Person's to it
for(i=0;i<rCount;i++){
fprintf(recordFile,"%s %s\n",records[i].fName,records[i].lName);
}
fclose(recordFile); //close the file
printf("Records saved to file: %s\n",fileName);
}
}
I was thinking of removing the do-while loops in the addRecords function and replacing them with if statements. And then finally an if statement to check the value of valid. And then if valid=0 point to a function or save the errorfile directly there.
However I am unsure if this is the best way to go (or if my thought process would even work) and wondered if anyone could help.
Edit: Decided to add the type of data I'm dealing with incase anyone wants to create a .txt and run the program
Bob Jones
Franklin Davies
James Donut
EDIT Following the answer below I have updated my code (edited segments below)
EDITED saveFunction
void save(int rCount, struct Person *records){
FILE *recordFile; //file handle
char fileName[30] = { '\0'}; //string to store the file name
int i;
puts("Enter a filename to save the records :"); //ask the user for the filename
scanf("%s", fileName); //store the filename: data input should be checked
//here in your program
//try and open the file for writing and react accordingly if there is a problem
if((recordFile = fopen(fileName,"w"))==NULL){
printf("Couldn't open the file: %s\n",fileName);
}
else{ //the file opened so print the records array of Person's to it
char fileName[sizeof (struct Person) * 2]; // twice needed size
while (fgets(fileName, sizeof fileName, recordFile) != NULL) {
struct Person P;
int n; // Save index where scanning stopped
int cnt = sscanf(fileName,"%15s%21s %n", P.fName, P.lName, &n);
if (cnt != 2 || fileName[n]) {
errorLine(fileName);
// do not increment i;
} else {
// Good to keep
// realloc memory as needed here
records[i] = P;
i++;
}
}
errorLine function:
void errorLine(char *fileName)
{
FILE *errorFile;
//try and open the file for writing and react accordingly if there is a problem
if((errorFile = fopen("error.txt","w"))==NULL){
printf("Couldn't open the file:\n");
}
else{ //the file opened so print the records array of Person's to it
for(i=0;i<rCount;i++){
fprintf(errorFile,"%i %s %s\n",records[i].fName,records[i].lName);
}
fclose(errorFile); //close the file
printf("Records saved to file: %s\n",fileName);
}
}
No doubt I probably implemented the answer incorrectly and now get an error:
error: expected declaration or statement at end of input
Which is found on my last line of the program
Need to limit length of input before attempting to save in structure.
else {
char buffer[sizeof (struct Person) * 2]; // twice needed size
while (fgets(buffer, sizeof buffer, recordFile) != NULL) {
struct Person P;
int n; // Save index where scanning stopped
int cnt = sscanf(buffer,"%15s%21s %n", P.fName, P.lName, &n);
if (cnt != 2 || buffer[n] || MaybeAddtionalTests(&P)) {
SaveBadLine(buffer);
// do not increment i;
} else {
// Good to keep
// realloc memory as needed here
records[i] = P;
i++;
}
}
fclose(recordFile); // close the file

How to store data from files to an array in C

So, basically this code below need the user to login first, then after the user login it will show the user details like the one stored in the user.txt. after that i dunno how to retrieve back the files from the files then return it as an array, so that i can update e.g change the name of the user, or delete the user details by taking the array index
here is my code
#include <stdio.h>
#include <string.h>
typedef struct {
char fullname[30];
char dob [10];
int contactNo;
int postcode;
}userDetails;
int main ()
{
char username [15];
char pwd [20];
char user_pass [30];
char userfile [100];
FILE *user;
FILE *admin;
userDetails myUser;
admin = fopen ("admin.txt","r");
printf("Please enter your Username\n");
scanf("%s",username);
printf("Please enter your Password\n");
scanf("%s",pwd);
user_pass[strlen(username) + strlen(pwd) + 2];
sprintf(user_pass, "%s;%s", username, pwd);
while (fgets(userfile, 100, admin) != NULL) {
if (strcmp(userfile, user_pass) == 0) {
printf("Authentication as %s successful\n", username);
size_t nread; // Printing the user information
user = fopen("user.txt", "r");
printf("\nHere is the registered user:\n");
if (user) {
while ((nread = fread(myUser.fullname, 1, sizeof myUser.fullname, user)) > 0)
fwrite(myUser.fullname, 1, nread, stdout);
if (ferror(user)) {
}
fclose(user);
}
}
else{
printf("Please enter correct username and password\n");
}
}
}
and let say in the user.txt the file is stored in a format like this
john;12/12/1990;+6017012682;57115
paul;12/12/1221;+60190002122;100022
max;12/11/1990;+60198454430;900000
jamie;12/05/2000;+60190001231;18000
Thank you
how do you read a data from a files and store it to an array, then read specific stored data in the array to be edited?
Steps to store data:
1) - Declare the type(s) of storage variables needed to support your concept. Array of struct perhaps.
2) - Open file ( FILE *fp = fopen("file path", "r"); )
3) - Loop on fgets() to read each line (record) of file
4) - Parse lines, perhaps using strtok and place elements of each line into storage variable you created above.
5) - close file ( fclose(fp); )
The get record part can be done by selecting a record, and returning a re-concatenated string composed of each field of the original record. The prototype could look like this:
void GetRecord(RECORD *pRec, int rec, char *recordStr);
Where storage would be created as an array of struct, and the struct would accommodate the 4 fields you cited, eg:
John;12/12/1990;+6017012682;57115 //example record with 4 fields
#define MAX_RECORDS 10 //arbitrary value, change as needed
typdef struct {
char name[260];
char date[11];
char num1;
char num2;
} RECORD;
RECORD record[MAX_RECORDS];//change size to what you need, 10 is arbitrary for illustration
Code Example to read data into records , and retrieve a record, could look like this:
(example only, very little error checking)
void GetRecord(RECORD *pRec, int rec, char *record);
int main(void)
{
FILE *fp = {0};
char recStr[260];
char *buf;
char line[260];
int i;
char strArray[3][7];//simple array, 3 string of 7 char each (enough for NULL terminator)
i = -1;
fp = fopen ("admin.txt", "r");
if(fp)
{
while (fgets (line, 260, fp))//will continue to loop as long as there is a new line
{
i++;
if(i >= MAX_RECORDS) break;//reached maximum records defined, time to leave
buf = strtok(line, ";");
if(buf)
{
strcpy(record[i].name, buf);
buf = strtok(NULL, ";");
if(buf)
{
strcpy(record[i].date, buf);
buf = strtok(NULL, ";");
if(buf)
{
record[i].num1 = atoi(buf);
buf = strtok(NULL, ";");
if(buf)
{
record[i].num2 = atoi(buf);
}
}
}
}
}
}
fclose(fp);
// 2nd argument valid values range from 1 - n (not 0 - n)
GetRecord(record, 3, recStr); //read third record into string "rec"
return 0;
}
void GetRecord(RECORD *pRec, int rec, char *record)
{
int r = rec - 1;//adjust for zero indexed arrays
if((r >= MAX_RECORDS) || (r < 0))
{
strcpy(record, "index error");
return;
}
sprintf(record, "%s;%s;%d;%d", pRec[r].name, pRec[r].date, pRec[r].num1, pRec[r].num2);
}
Note: "coz [you were] rushing and just copy the code, and accidentaly delete the important things",
I have not adhered strictly to your variable names. Adjust what I have done to meet your needs.
Results with your input file look like this:

Why isn't fread reading in the correct integer?

This question was just to clarify why the correct output(year) isn't being printed correctly, given the following code.
For future readers, the issue was that the fget() function read off a byte that was supposed to be a byte that belonged to year, causing the year to not be printed correctly.
Here is my code
Car structure
typedef struct carType Car;
struct carType {
int vehicleID;
char make[20];
char model[20];
int year;
int mileage;
double cost;
Car *next;
};
Code to write to binary file
void writeBinFile(Car *headPointer)
{
char fileName[20];
//prompt user for name of textfile to print to
scanf("%s", fileName);
FILE *ftp;
Car *start = headPointer->next;
ftp = fopen(fileName, "wb");
char separator = '0';
while(start != NULL)
{
//write out 1 cell of data, cell contains 4 bytes
fwrite(&start->year,sizeof(int), 1, ftp);
fwrite(start->make,sizeof(char), strlen(start->make), ftp);
fwrite(&separator, sizeof(char), 1, ftp);
fwrite(start->model, sizeof(char), strlen(start->make), ftp);
fwrite(&separator, sizeof(char), 1, ftp);
fwrite(&start->cost, sizeof(float), 1, ftp);
fwrite(&start->mileage, sizeof(float),1,ftp);
fwrite(&start->vehicleID, sizeof(int), 1, ftp);
start = start->next;
}
fclose(ftp);
}
And code to read in a binary file
void readFromBinFile(Car *headPointer)
{
char fileName[20];
//prompt user for name of textfile to print to
scanf("%s", fileName);
FILE *ftp;
Car *previous = headPointer;
ftp = fopen(fileName, "rb");
Car *current;
//go until the end of file is reached
int c;
while((c = fget(ftp)) != EOF)
{
current = (Car *)malloc(sizeof(Car));
previous->next = current;
//program receives 1 cell, that cell contains 4 bytes
fread(&current->year, sizeof(int),1,ftp);
printf("%d\n",current->year);
char make[25];
int count = 0;
char oneAtATime= 'a';
while(oneAtATime != '0')
{
fread(&oneAtATime, sizeof(char),1,ftp);
if(oneAtATime!='0')
{
make[count] = oneAtATime;
count++;
}
}
make[count] = 0;
strcpy(current->make, make);
char model[25];
count = 0;
oneAtATime= 'a';
while(oneAtATime != '0')
{
fread(&oneAtATime, sizeof(char),1,ftp);
if(oneAtATime!='0')
{
model[count] = oneAtATime;
count++;
}
}
model[count] = 0;
strcpy(current->model, model);
fread(&current->cost, sizeof(float),1, ftp);
fread(&current->mileage, sizeof(int),1,ftp);
fread(&current->vehicleID, sizeof(int),1,ftp);
previous = previous->next;
}
fclose(ftp);
}
Here is my data that I am trying to write/read from bin file.(loaded this from the read/write to text which works)
2014 Toyota Rav4 cost:$40000 mileage:3000, vehicleID:1
2014 Toyota Celica cost:$3220 mileage:2222, vehicleID:3
In the read binary method, I use this line of code for debugging purposes
printf("%d\n",current->year);
Which should print 2014 in both instances. However when i try running the code, this is what gets printed
1275068423
1811939335
Is there something i am missing in my read and my write method that is causing the year to be so off?
You have a logic error. The line
while((c = fgetc(ftp)) != EOF) // Assuming you meant to use fgetc not fget
reads the first character from ftp that is supposed to be part of the current->year. When you execute
fread(&current->year, sizeof(int),1,ftp);
you are reading the wrong data. If sizeof(int) is 4 in your platform, you are picking up 3 bytes of data from the saved year and one byte from the saved make.
My suggestion to fix it:
int year;
while(1)
{
if ( fread(&year, sizeof(int),1,ftp) != 1 )
{
// No more data to read.
break;
}
current = (Car *)malloc(sizeof(Car));
previous->next = current;
current->year = year;
// Proceed with reading the rest of the data.
// Make sure you check the return value of every call
// to `fread` before proceeding to the next line of code.

How to resolve segmentation error in reading/writing to binary file in C

This is the struct definition that I am trying to write copies of to and read from binary file
typedef struct carType Car;
struct carType {
int vehicleID;
char make[20];
char model[20];
int year;
int mileage;
double cost;
Car *next;
};
This is my code for writing to a binary file(state of the Car)
void writeBinFile(Car *headPointer)
{
char fileName[20];
//prompt user for name of textfile to print to
scanf("%s", fileName);
FILE *ftp;
Car *start = headPointer->next;
ftp = fopen(fileName, "wb");
char separator = '0';
while(start != NULL)
{
//write out 1 cell of data, cell contains 4 bytes
fwrite(&start->year,sizeof(int), 1, ftp);
fwrite(start->make,sizeof(char), strlen(start->make), ftp);
fwrite(&separator, sizeof(char), 1, ftp);
fwrite(start->model, sizeof(char), strlen(start->make), ftp);
fwrite(&separator, sizeof(char), 1, ftp);
fwrite(&start->cost, sizeof(float), 1, ftp);
fwrite(&start->mileage, sizeof(float),1,ftp);
fwrite(&start->vehicleID, sizeof(int), 1, ftp);
start = start->next;
}
fclose(ftp);
}
This is my code for reading from a binary file(to state of the car)
void readFromBinFile(Car *headPointer)
{
char fileName[20];
//prompt user for name of textfile to print to
scanf("%s", fileName);
FILE *ftp;
Car *previous = headPointer;
ftp = fopen(fileName, "rb");
Car *current;
//go until the end of file is reached
while(!feof(ftp))
{
current = (Car *)malloc(sizeof(Car));
previous->next = current;
//program receives 1 cell, that cell contains 4 bytes
fread(&current->year, sizeof(int),1,ftp);
printf("%d\n",current->year);
char make[25];
int count = 0;
char oneAtATime= 'a';
while(oneAtATime != '0')
{
fread(&oneAtATime, sizeof(char),1,ftp);
if(oneAtATime!='0')
{
make[count] = oneAtATime;
count++;
}
}
make[count] = 0;
strcpy(current->make, make);
char model[25];
count = 0;
oneAtATime= 'a';
while(oneAtATime != '0')
{
fread(&oneAtATime, sizeof(char),1,ftp);
if(oneAtATime!='0')
{
model[count] = oneAtATime;
count++;
}
}
model[count] = 0;
strcpy(current->model, model);
fread(&current->cost, sizeof(float),1, ftp);
fread(&current->mileage, sizeof(int),1,ftp);
fread(&current->vehicleID, sizeof(int),1,ftp);
previous = previous->next;
}
fclose(ftp);
}
Last time I got a segmentation error from not allocating memory to the new car Why am I getting a segmentation failure?. I made sure to do that this time. I checked this one Segmentation fault when reading a binary file into a structure and Segmentation fault while reading binary file in C but my fields were values , not pointers.
Does anyone see a glaring issue? I can't test anything bc whenever i try to run this, i get that error. The problem seems to be the reading but i am not sure if some code in the writing is causing the reading to fail
'0' is not the null terminator. 0 or '\0' are (note the lack of quotes on the first and the escape char on 2nd). '0' is value 48, not zero.
These are valid options.
char separator = 0;
or
char separator = '\0';
You have an error:
fwrite(start->model, sizeof(char), strlen(start->make), ftp); // 'make' size used to write 'model'
Secondly, you can simplify your code rather than writing the separator null as a separate step, just write out the full string, including null terminator.
fwrite(start->make, 1, strlen(start->make) + 1, ftp);
However, how do you intend to read the strings back in? What function call are you going to use to read a string in binary that maybe variable length, with a null terminator? Better would be to just write the padded buffer using sizeof instead of strlen.
fwrite(start->make, 1, sizeof(start->make), ftp);
However, even this is brittle, because sizeof() will silently return a different value if your struct members are changed from a fixed character array to a character string (pointer). You are safer using constants.
const int SMALL_STRING_LEN = 20;
const int MAKE_LEN = SMALL_STRING_LEN;
const int MODEL_LEN = SMALL_STRING_LEN;
char make[MAKE_LEN];
char make[MODEL_LEN];
fwrite(start->model, 1, MODEL_LEN, ftp);
fwrite(start->make, 1, MAKE_LEN, ftp);

Resources