I'm working on creating a student database in C. The final thing I need to be able to do read and write the database I create to a file. So I've already got an array full of pointers to student structures, and I need to write it to a file. Once I have it written, I need to be able to read it back into my array as well.
I'm really not sure how to do it though. This is my struct:
typedef struct student Student;
struct student
{
char name[300];
int age;
char course1[300];
char course2[300];
char remarks[300];
};
Student *Data[30];
And these are the functions I've written to work with file:
void writeDisk()
{
FILE *file = fopen("disk.dat", "ab");
fwrite(&Data, sizeof(Student), count, file);
fclose(file);
}
void loadDisk()
{
FILE *file = fopen("disk.dat", "rb");
if (file != NULL)
{
fread(&Data, sizeof(Student), count, file);
fclose(file);
}
}
void emptyDisk()
{
FILE *file = fopen("disk.dat", "rw");
fopen("disk.dat", "wb");
}
I can see that the size of my disk.dat file changes when I write to it, and it goes to zero when I call empty, but loading does not work at all. If the student array in my program is zero, it just stays at zero when I call load and try to display it, but if there is something in the array, I get a segmentation fault when I call load and then try to display it.
I may be doing this entirely wrong. I'm really not sure what I'm doing. I was thinking that I could just write the whole array to a file, but I'm not sure that's true. I was just wondering if someone could look at this and let me know where I'm going wrong.
EDIT
I've edited my code to look like this:
void writeDisk()
{
int i = 0;
FILE *file = fopen("disk.dat", "ab");
for(i; i <count; i++)
{
fwrite(Data[i], sizeof(Student), 1, file);
}
fclose(file);
}
void loadDisk()
{
char buffer[300];
int i = 0;
clearData();
FILE *file = fopen("disk.dat", "rb");
while(Data[i] != NULL)
{
Data[i] = malloc(sizeof(Student));
fread(Data[i], sizeof(Student), 1, file);
i++;
}
fclose(file);
}
This still doesn't work though. The write seems to work better,but I don't see it writing the age of the student over to the file. The clearData() function in the load file just clears anything that's in the Data array to begin with, so that we can have a clean slate to read the file into.
I believe instead of
Student *Data[30];
You want
Student Data[30];
Because the first one is an array of pointers, while the second one is an array of the struct you want.
When you write
fread(&Data, sizeof(Student), count, file);
It reads the data from the file right into the location of Data. It looks like you want to read and write the actual structs, so you have to put them directly into the array, as opposed to using pointers.
I think this is the culprit:
Student *Data[30];
That's an array of pointers to Student structures. There is no storage allocated for actual Students.
Remove the *, throughout the rest of the code you seem to properly use Data as if it was a plain array, so it should need no modification.
EDIT on an unrelated note, you can declare a structure and its alias on the same statement, like this:
typedef struct {
...
} Student;
If Data is indeed an array of pointers to your structures, then what you are saving is just the pointers and not your actual data. In fact, you should never save actual pointers as the next time you run, malloc may return different pointers for storing your data.
What you want to do, for saving, is to iterate over your array and write the actual structure data to the file, something like:
for (i = 0; i < numberOfStudents; i++) {
fwrite(Data[i], sizeof (Student), 1, file);
}
For restoring, you'll need to loop over the students, malloc storage and read in the structure, and then set the Data pointer, something like:
for (i = 0; i < numberOfStudents; i++) {
Student *p = malloc(sizeof (Student));
fread(p, sizeof (Student), 1, file);
Data[i] = p;
}
Also, in general, you should check the return values from fopen, fread, fwrite, and fclose to detect errors.
Related
I'd like to add an array of type 'struct classes' (definition included below) to a file. For instance, if allClasses[0].title is equal to "Math" and allClasses[0].class_id is equal to 1, I'd like the file to have the following input:
1Math/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0
If another class is added with a title of Science, then the file should now read
1Math/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/02Science/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0
What appears to happen is that, although the code will catch the char array part of the struct (math / science in the example), it will have trouble with the int and instead print out junk in its place (and the junk is often times longer than 1 character / 1 int long).
From experience, the code format (with a few adjustments, of course) works just fine when using a struct with variables that are only char arrays. However, it glitches out when using ints. Is this because of ASCII conversions, or something similar? How can I adjust the code so that I get the input with both the int and the char array?
void addClasses(char *given_title) {
FILE *fp;
fp = fopen("classes.db", "r");
if (numClasses == 0 && fp != NULL) {
findClasses();
}
strcpy(allClasses[numClasses].title, given_title);
allClasses[numClasses].class_id = numClasses + 1;
numClasses++;
fclose(fp);
fp = fopen("classes.db", "w");
for (int i = 0; i < numClasses; i++) {
struct classes *object = malloc(sizeof(struct classes) - 1);
memset(object, 0, sizeof( struct classes ));
object->class_id = allClasses[i].class_id;
strcpy(object->title, allClasses[i].title);
fseek(fp, numClasses * (sizeof(struct classes) - 1), SEEK_END);
fwrite(object, sizeof(struct classes) - 1, 1, fp);
}
fclose( fp );
}
The struct:
struct classes {
int class_id;
char title[30];
};
A bit of extra (possibly unnecessary) background on some of the components in the code: the bit at the beginning of the method tries to read the file and start to fill the array with any structs that were already put into the file before starting the program. I'm not including the code for that, since the aforementioned glitch happens even when I have a fresh classes.db file (and thus, even when findClasses() never runs).
Small note, by the way: I can't change the class_id into a char / char array. It needs to be an int.
If you want to add it in the text form:
fprintf(fp, "%d,\"%s\"\n", object -> class_id, object -> title);
when you open the file with "w" you create new empty file. When you write to the file you do need to fseek.
If you want to append to existing file use "a" or "a+" instead.
This question already has answers here:
Writing and reading (fwrite - fread) structures with pointers
(3 answers)
Closed 8 years ago.
I tried to write and read from a file with pointers in structures. But when I read from file I see some garbage value. I am using GCC 4.7.2 on Linux. Need some help.
Read:
//read from a file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE * fp = NULL;
fp = fopen("/media/Study/PhoneDirectory.dat","rb");
if(fp == NULL)
printf("Error opening file!!!");
fseek(fp,0,SEEK_SET);
if(fread(&phoneList[1],sizeof(LISTING),1,fp)==1)
printf("%s %s",phoneList[1].name,phoneList[1].phone);
fclose(fp);
return 0;
}
And write:
//Write to file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[2];
FILE * fp = NULL;
fp = fopen("/media/Study/PhoneDirectory.dat","wb");
phoneList[1].name = "Santosh";
phoneList[1].phone = "9657681798";
if(fwrite(&phoneList[1],sizeof(LISTING),1,fp)==1)
printf("inserted");
fclose(fp);
return 0;
}
Pointers are only meaningful in the application process that they originate from. If you write them to a file, as you're doing here, the values you read back will be meaningless — they will most likely point to uninitialized memory, or to memory which is being used for something else entirely.
You will need to come up with another way of writing this data to a file.
The problem you have is equivocating between char* and char[]. You can certainly assign a string literal to a char*, but you need to understand what the contents of a LISTING structure contain, and how you want to serialize and deserialize data to a file.
It does not make sense to save pointers from one process and read them into another process, so you probably want to save the contents (what a pointer points at). You want to store two values, (name, phone) to the file. Since you likely want to store the literal name and literal phone, let us consider what the file might look like:
roast duck|212-333-4444
peking duck|411-511-61111
duck soup|314-222-3333
free duck|800-111-2222
...
You need functions to serialize and deserialize your data. Since your LISTING type is pointers, you will need to allocate appropriate space for those values, as you read them, and you need functions (methods) to read serialized data from a file and write serialized data to a file.
Reading (you will need to allocate enough space),
int
listing_read(FILE*fp, LISTING* listing)
{
char name_buffer[100];
char phone_buffer[100];
if(!fp) return(-1);
if(!listing) return(-2);
int res = fscanf(fp,"%s|%s\n",name_buffer,phone_buffer);
if( !res ) {
//handle error here
}
//careful here, you cannot free if you didn't malloc/strdup
if(listing->name) free(listing->name);
if(listing->phone) free(listing->phone);
listing->name = strdup(name_buffer);
listing->phone = strdup(phone_buffer);
return(0);
}
Writing (you will need to provide proper formatting),
int
listing_write(FILE*fp, LISTING* listing)
{
if(!fp) return(-1);
if(!listing) return(-2);
fprintf(fp,"%s|%s\n",listing->name,listing->phone);
return(0);
}
Here is how you need to modify your code,
//read from a file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE* fp = NULL;
if( !(fp = fopen("/media/Study/PhoneDirectory.dat","rb")) ) {
printf("Error opening file!!!");
exit(1);
}
fseek(fp,0,SEEK_SET);
if( listing_read(fp,&phoneList[0]) >= 0 ) {
printf("%s %s",phoneList[0].name,phoneList[0].phone);
}
fclose(fp);
return 0;
}
And here is how writing the file would change,
//Write to file
#include<stdio.h>
typedef struct
{
char* name;
char* phone;
}LISTING;
int main(void)
{
LISTING phoneList[14];
FILE* fp = NULL;
if( !(fp = fopen("/media/Study/PhoneDirectory.dat","wb")) ) {
printf("error, cannot write file\n");
exit(1);
}
phoneList[0].name = "Santosh";
phoneList[0].phone = "9657681798";
if( listing_write(fp,&phoneList[0])>=0) {
printf("inserted");
}
fclose(fp);
return 0;
}
Note that in you writing program you assign the string literals "Santosh" and "9657681798" to the LISTING members name and phone. Though legal to do, you need a better understanding of what C does here. C takes the address of these C-string constants and assigns those addresses to the phonelist[1].name and phonelist[1].phone member pointers.
Consider that if you did this assignment,
phoneList[0].name = "Santosh";
phoneList[0].phone = "9657681798";
You have assigned the pointers to constant strings to your structure members.
But if you were to allocate space (for example, using strdup()),
phoneList[0].name = strdup("Santosh");
phoneList[0].phone = strdup("9657681798");
You have allocated space for the strings, assigning independent locations for these member elements. Which is is more likely what you want to do.
Note that I used phonelist[0] since C has zero-based arrays.
printf("%s %s",phoneList[1].name,phoneList[1].phone);
The above statement invokes undefined behaviour.
Since the pointers name & phone of struct object phoneList[1] are not initialized dereferencing them invokes UB. In your case they are throwing out garbage values but it could have lead to a crash also.
To fit your case of reading the contents of file and storing it in the struct objects use getline function to read them row-wise(assuming that all the details are stored line-wise) and then dynamically allocate the memory for char pointers then assign them to the read value. But, this approach leads to lot of memory management which is error prone.
student programmer here. I am having trouble reading input from a binary file in C. The data stored in the file are structs that look like this:
typedef struct reserve{
char *name;
char *ic;
int room_number;
} Reserve;
I get no problem when I write the struct to a binary file. Here is the code I did for writing to the file:
void reg_new() //Make a new reservation
{
Reserve newReserve = {"", "", 0};
char name[NAMEFIELD], ic[ICFIELD];
int room_number;
FILE *resvptr;
scanf_s("%s", name, NAMEFIELD);
scanf_s("%s", ic, ICFIELD);
scanf_s("%d", &room_number);
newReserve.name = name;
newReserve.ic = ic;
newReserve.room_number=room_number;
resvptr = fopen("reservations.dat", "wb");
fwrite(&newReserve, sizeof(Reserve), 1, resvptr);
fclose(resvptr);
}
And the code for reading from the file:
void reg_view() //view a reservation.
{
FILE *seekptr;
Reserve viewReserve = {"", "", 0};
int read;
if ( (seekptr = fopen("reservations.dat", "rb")) == NULL)
{
puts("Error: file could not be found.");
}
else
{
while ( !feof(seekptr))
{
read = fread(&viewReserve, sizeof(Reserve), 1, seekptr);
if(read != 0)
{
printf("Name: %s\nIC number: %s\nRoom Number: %d\n", viewReserve.name, viewReserve.ic, viewReserve.room_number);
}
}
fclose(seekptr);
}
Immediately after writing to the binary file and accessing the function for reading, the room number (of int value) reads fine, but the two strings (name and IC number) read out as garbage characters. Subsequently, closing and restarting the program, then attempting to read from the file will give me an access violation and bad pointer error.
The trouble is with reading the strings from the file. I'm suspecting a memory leak but I can't say for sure...can anyone help? Thanks.
You are writing pointers to the file, not the actual data. When you read the pointer from the file, it's just a memory address that you probably don't have access to, even if you do it in the same process that wrote the file (because it was actually a pointer to a function-scoped stack variable).
If you want to store the records in that way, make your function store the actual string data rather than the pointer:
typedef struct reserve{
char name[NAMEFIELD];
char ic[ICFIELD];
int room_number;
} Reserve;
And just do this:
scanf_s("%s", newReserve.name, NAMEFIELD);
scanf_s("%s", newReserve.ic, ICFIELD);
scanf_s("%d", &newReserve.room_number);
The structure consists of the integer and the addresses of two arrays (pointers). When you write it out and read it back, you are reading and writing the addresses of the character arrays - not their contents. You need to modify your routine to write and read the contents (including lengths) and also to allocate the storage to hold the contents when you read them back in.
Recently started working with pointers and have created a little script that is supposed to stich together some textfiles.
However when i try to call fputs i get a coredump/segmentation error. I suspect it is because of the way that the file pointer is saved. I find the files saves it in an array and tries to retrieve it later on.
the FILE pointer is saved in a struct. Does somebody instantly spot my fault? i would be very grateful!
The struct:
typedef struct{
int listSize;
int listCapacity;
FILE *fileStream;
}FileList;
Creating the struct
FileList fileList;
fileList.listSize=0;
fileList.listCapacity=1;
fileList.fileStream=calloc(fileList.listCapacity,sizeof(FILE));
and then i add the struct to the array by calling
void addFile(FileList* list, FILE* file)
{
list->fileStream[list->listSize]=*file;
}
However when i call
char* buffer[10];
size_t result=0;
result = fread(buffer,1,10,&fileList.fileStream[ii+currentGroupOffset]);
fputs(*buffer,outPutFile);
it crashes, i tried to watch the value ii+currentGroupOffset making sure it doesnt go out the array bounds
any help at all appriciated! :)
You can't allocate and copy around FILE structures yourself - it's an opaque data type. So, instead of creating an array of FILE structures, create an array of FILE * pointers:
typedef struct {
int listSize;
int listCapacity;
FILE **fileStream;
} FileList;
FileList fileList;
fileList.listSize = 0;
fileList.listCapacity = 1;
fileList.fileStream = calloc(fileList.listCapacity, sizeof fileList.fileStream[0]);
then add a FILE * pointer to the array by copying the pointer value:
void addFile(FileList *list, FILE *file)
{
list->fileStream[list->listSize] = file;
}
and use it like so:
char buffer[10];
size_t result = 0;
result = fread(buffer, 1, 10, fileList.fileStream[ii+currentGroupOffset]);
fwrite(buffer, 1, result, outPutFile);
It seems you want a dynamically allocated array of FILE* elements. You have:
FILE *fileStream;
That can either be treated as a FILE pointer, or an array of FILE elements. But not as an array of FILE pointers. For that, you need:
FILE **fileStream;
And allocating the array should be done with:
fileList.fileStream=calloc(fileList.listCapacity,sizeof(FILE*));
FILE is not a type you use directly. You always deal with pointers to it. You should treat it as an opaque type.
Also, I don't see where you actually open the files (using fopen()) anywhere in your code.
Why
char * buffer[10];
It should be
char buffer[10];
Where is list->listSize incremented?
I don't understand what this is
fileList.fileStream=calloc(fileList.listCapacity,sizeof(FILE));
FILE *s are initialized by calling fopen not by allocating memory
I have a structure with the following definition:
typedef struct myStruct{
int a;
char* c;
int f;
} OBJECT;
I am able to populate this object and write it to a file. However I am not able to read the char* c value in it...while trying to read it, it gives me a segmentation fault error. Is there anything wrong with my code:
//writensave.c
#include "mystruct.h"
#include <stdio.h>
#include <string.h>
#define p(x) printf(x)
int main()
{
p("Creating file to write...\n");
FILE* file = fopen("struct.dat", "w");
if(file == NULL)
{
printf("Error opening file\n");
return -1;
}
p("creating structure\n");
OBJECT* myObj = (OBJECT*)malloc(sizeof(OBJECT));
myObj->a = 20;
myObj->f = 45;
myObj->c = (char*)calloc(30, sizeof(char));
strcpy(myObj->c,
"This is a test");
p("Writing object to file...\n");
fwrite(myObj, sizeof(OBJECT), 1, file);
p("Close file\n");
fclose(file);
p("End of program\n");
return 0;
}
Here is how I am trying to read it:
//readnprint.c
#include "mystruct.h"
#include <stdio.h>
#define p(x) printf(x)
int main()
{
FILE* file = fopen("struct.dat", "r");
char* buffer;
buffer = (char*) malloc(sizeof(OBJECT));
if(file == NULL)
{
p("Error opening file");
return -1;
}
fread((void *)buffer, sizeof(OBJECT), 1, file);
OBJECT* obj = (OBJECT*)buffer;
printf("obj->a = %d\nobj->f = %d \nobj->c = %s",
obj->a,
obj->f,
obj->c);
fclose(file);
return 0;
}
When you write your object, you're writing the pointer value to the file instead of the pointed-to information.
What you need to do is not just fwrite/fread your whole structure, but rather do it a field at a time. fwrite the a and the f as you're doing with the object, but then you need to do something special with the string. Try fwrite/fread of the length (not represented in your data structure, that's fine) and then fwrite/fread the character buffer. On read you'll need to allocate that, of course.
Your first code sample seems to assume that the strings are going to be no larger than 30 characters. If this is the case, then the easiest fix is probably to re-define your structure like this:
typedef struct myStruct{
int a;
char c[30];
int f;
} OBJECT;
Otherwise, you're just storing a pointer to dynamically-allocated memory that will be destroyed when your program exits (so when you retrieve this pointer later, the address is worthless and most likely illegal to access).
You're saving a pointer to a char, not the string itself. When you try to reload the file you're running in a new process with a different address space and that pointer is no longer valid. You need to save the string by value instead.
I would like to add a note about a potential portability issue, which may or may not exist depending upon the planned use of the data file.
If the data file is to be shared between computers of different endian-ness, you will need to configure file-to-host and host-to-file converters for non-char types (int, short, long, long long, ...). Furthermore, it could be prudent to use the types from stdint.h (int16_t, int32_t, ...) instead to guarantee the size you want.
However, if the data file will not be moving around anywhere, then ignore these two points.
The char * field of your structure is known as a variable length field. When you write this field, you will need a method for determining the length of the text. Two popular methods are:
1. Writing Size First
2. Writing terminal character
Writing Size First
In this method, the size of the text data is written first, followed immediately by the data.
Advantages: Text can load quicker by block reads.
Disadvantages: Two reads required, extra space required for the length data.
Example code fragment:
struct My_Struct
{
char * text_field;
};
void Write_Text_Field(struct My_Struct * p_struct, FILE * output)
{
size_t text_length = strlen(p_struct->text_field);
fprintf(output, "%d\n", text_length);
fprintf(output, "%s", p_struct->text_field);
return;
}
void Read_Text_Field(struct My_STruct * p_struct, FILE * input)
{
size_t text_length = 0;
char * p_text = NULL;
fscanf(input, "%d", &text_length);
p_text = (char *) malloc(text_length + sizeof('\0'));
if (p_text)
{
fread(p_text, 1, text_length, input);
p_text[text_length] = '\0';
}
}
Writing terminal character
In this method the text data is written followed by a "terminal" character. Very similar to a C language string.
Advantages: Requires less space than Size First.
Disadvantages: Text must be read one byte at a time so terminal character is not missed.
Fixed size field
Instead of using a char* as a member, use a char [N], where N is the maximum size of the field.
Advantages: Fixed sized records can be read as blocks.
Makes random access in files easier.
Disadvantages: Waste of space if all the field space is not used.
Problems when the field size is too small.
When writing data structures to a file, you should consider using a database. There are small ones such as SQLite and bigger ones such as MySQL. Don't waste time writing and debugging permanent storage routines for your data when they have already been written and tested.