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(¤t->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(¤t->cost, sizeof(float),1, ftp);
fread(¤t->mileage, sizeof(int),1,ftp);
fread(¤t->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(¤t->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.
Related
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.
I am doing a small project for college (1st semester doing a bookstore implementation) and I have a problem with reading from a text file into a list of structs, with a two-dimensional array of characters in it that stores authors. However, it doesn't work properly (every time I launch the program it shows list is empty). Writing to a file works (I think, because it overwrites my text file with empty data).
Example data:
Adam Mickiewicz///Pan Tadeusz/Publisher 1/1833/24.99
Jules Verne///Around The World in 80 days/Publisher 1/1904/19.99
Jean-Jacques Sempe/Rene Goscinny//Little Nicholas/Publisher 2/1963/22.99
My structure:
#define AK 3 // Constant denoting max number of authors
typedef struct
{
char authors[AK][100];
char title[255];
char publisher[100];
unsigned int year;
double price;
struct Book *next;
} Book;
Book *first; // Address of the first element in a list
Reading from file:
void read_from_file(const char *path)
{
int no_of_authors;
int i;
printf("Loading...\n");
FILE *fp = fopen(path, "r"); // Opening a file
// Checking for errors
if (!fp) {
printf("Error reading from file!");
return;
}
// The loading mechanism
no_of_authors = 0;
while (!feof(fp)) {
Book *new = (Book*) malloc(sizeof(Book));
for (i = 0; i < AK; i++) {
fscanf(fp, "%s/", new->authors[i]);
}
fscanf(fp, "%s/%s/%u/%lf", new->title, new->publisher,
&new->year, &new->price);
fscanf(fp, "\n");
new = new->next;
}
fclose(fp);
printf("Loading successful.");
}
Writing to file (just in case):
void write_to_file(const char *path, Book *first)
{
int i;
printf("Saving...\n");
FILE *fp = fopen(path, "w");
Book* current = first;
if (!fp) {
printf("Error opening the file!");
dump_list(first); // Dumping the list to prevent memory leaks, this works okay
}
// Saving mechanism
while (first != NULL) {
for (i = 0; i < AK; i++) {
fprintf(fp, "%s/", current->authors[i]);
}
fprintf(fp, "%s/%s/%u/%lf", current->title, current->publisher,
¤t->year, ¤t->price);
fprintf(fp, "\n");
}
fclose(fp);
printf("Saved successfully");
}
OP's biggest failing is not checking the return value of fscanf(). Had code done so, problems would be more rapidly detected.
When is comes to reading lines of data the first consideration is:
Could input be faulty?
With learner applications this is often considered no. Input is either "good" or end-of-file. Let us not assume data is too well formated.
As it turns out, while the data file may not be faulty, the code reading it may be wrong. The subtle 2nd reason for code to check the *scanf() return values - self validation.
For line orientated data, it is much better to read is a line of data with fgets() than feof(), fscanf()... See also #Paul Ogilvie
char buf[sizeof(Book) * 2]; // Use an ample sized buffer
while (fgets(buf, sizeof buf, fp)) {
Use "%s" to read in text that does not include white-space. This will also read in '/'. Since '/' is use to delimit, "%s" is not an acceptable input specifier. Further names like "Adam Mickiewicz" include a space. A 2nd reason to not use "%s".
Consider what fscanf(fp, "%s/", new->authors[i]); is doing. "%s" scans into new->authors[i] non-white-space characters. A character after non-white-space characters is a white-space, never a '/'.
Use "%[^/]" to read in text that does not include '/'.
Use "%n" to keep track of the current scan offset.
Now parse the line.
char *p = buf;
Book *nu = malloc(sizeof *nu); // no cast needed
for (size_t i = 0; i < AK; i++) {
int n = 0;
sscanf(p, "%99[^/]/%n", nu->authors[i], &n);
if (n == 0) {
Handle_Parse_Error();
}
p += n;
}
if (sscanf(p, "%254[^/]/%99[^/]/%u/%lf",
nu->title, nu->publisher, &nu->year, &nu->price) != 4) {
Handle_Parse_Error();
}
Robust code would add checks on each member. Suit to coding goals.
if (nu->year < -6000 || nu->year > 2999) Fail_Year_Range();
Further work is needed to link data together. OP's code is unclear on this matter and so left to OP. A possible approach:
Book *read_from_file(const char *path) {
Book first; // Only the .next member is used.
first.next = NULL;
Book *current = &first;
// setup while () loop
...
while (fgets(buf, sizeof bu, fp)) {
...
Book *nu = malloc(sizeof *nu);
nu->next = NULL;
...
// parse data, fill in rest of nu->
....
current->next = nu; // update current pointer
current = nu;
}
// close up file ...
return first.next;
}
So I am trying to write a c file that has a student record database, once the file runs it is supposed to read student records from a binary file and add them to a linked list: the student struct looks like this :
typedef struct student {
char lname[10], initial, fname[10];
unsigned long SID;
float GPA;
struct student* next; /* pointer to the next student record */
} studentList;
I am using single linked lists for my data, and my code runs fine if I read and write the data with fscanf. However, once I started using fwrite and fread, everytime my program loads it would not load up the data from the text file correctly, when I check the binary file it seems it has data in it. Here is what I have for my load and write data functions:
void printRecords() {
FILE *fPointer = fopen("data.bin", "w");
studentList *newStudent = head;
while (newStudent != NULL) { /*Loop through linked list starting from head node*/
fwrite(&newStudent, sizeof(newStudent), 1, fPointer);
newStudent = newStudent->next;
}
}
void loadRecords() {
studentList *cStudent;
FILE *fPointer = fopen("data.bin", "r");
int counter = 0;
int x = 0;
int n = 0;
while (n != 0) {
printf("test\n");
if (fPointer == NULL) {
break;
}
cStudent = (studentList *)malloc(sizeof(studentList));
n = fread(&cStudent, sizeof(cStudent), 1, fPointer);
x = cStudent->GPA;
printf("%d\n", x);
if (feof(fPointer)) { break; }
if (counter == 0) {
head = cStudent;
temp = (studentList *)malloc(sizeof(studentList));
temp = cStudent;
counter++;
}
temp->next = (studentList *)malloc(sizeof(studentList));
temp->next = cStudent;
temp = temp->next;
}
fclose(fPointer);
}
so what am I doing wrong as right now it does not read anything into my list, it seems like it writes but not sure if it even writes the correct data, I have spent a long time trying to figure this out and have been stuck on it for a while now, thanks in advance.
To read write to binary files in c.
FILE *fPointer=fopen("data.bin","wb"); //to write file
FILE *fPointer=fopen("data.bin","rb"); //to read file
There are multiple problems with your code.
printRecords has problems:
you should use binary mode.
you write the contents of the pointer and some indeterminate contents instead of whet the pointer points to. This is actually undefined behavior.
you forgot to close the file.
the value written to the file for the next member is meaningless. Writing the same records may produce different file contents for different runs.
Here is a corrected version that returns the number of records written or -1 if the file could not be opened:
int printRecords(void) {
FILE *fPointer = fopen("data.bin", "wb");
studentList *sp;
int n = 0;
if (fPointer == NULL)
return -1;
/* Loop through linked list starting from head node */
for (sp = head; sp != NULL; sp = sp->next) {
n += fwrite(sp, sizeof(newStudent), 1, fPointer);
}
fclose(fPointer);
return n;
}
loadRecords has even more problems:
binary mode should be used too.
the way you test for end of file is incorrect.
the way you link records does not work either.
Here is a corrected version of loadRecords that returns the number of records read or -1 if the file could not be opened:
int loadRecords(void) {
studentList student, *newsp, *sp;
FILE *fPointer = fopen("data.bin", "rb");
int n = 0;
if (fPointer == NULL)
return -1;
while (fread(&student, sizeof(student), 1, fPointer) == 1) {
n++;
student.next = NULL; // value read from file is meaningless
newsp = malloc(sizeof(studentList));
*newsp = student;
if (head == NULL) {
head = newsp;
} else {
/* append the new record to the list */
for (sp = head; sp->next; sp = sp->next)
continue;
sp->next = newsp;
}
}
fclose(fPointer);
return n;
}
Be aware that storing binary data to the file system this way is not portable. The representation of integers and floats may differ from one platform to another, as well as alignment of the structure members, especially the next pointer which is useless in the file anyway. This file should only be read back on the same platform, with the same program that wrote it, making it a poor choice for backup or persistent storage.
There are problems in the file write function, mainly the data pointer and size:
void printRecords(){
FILE *fPointer = fopen("data.bin","wb"); // add "b" mode (MSVC)
if (fPointer == NULL) // check file opened
exit(1); // or report "Cannot open file"
studentList *newStudent = head;
while(newStudent !=NULL)
{
fwrite(newStudent, sizeof(*newStudent), 1, fPointer); // remove the & and add a *
newStudent = newStudent->next;
}
if (fclose(fPointer)) // close the file
exit(1); // or report "Failed to close 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
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(¤t->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(¤t->cost, sizeof(float),1, ftp);
fread(¤t->mileage, sizeof(int),1,ftp);
fread(¤t->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);