Loading structure from txt file to dynamic array - c

I have trouble with saving and loading data from .txt file to dynamic array.
My whole program is based on switch statement.
I will only paste here case which is responsible for allocating memory for array and calling function to fill it with structure. And functions for saving and loading data.
Code looks like this
case 1:
system("cls");
printf("Enter amount of students you want to put in data base\n");
scanf("%d",&number_of_students);
student=(data*)malloc(number_of_students*sizeof(data));
adding_students_to_base( number_of_students); // its a simple functions based on for loop. I don't think that posting it here is necessary
break;
and functions:
void saving_base_to_file(int amount_of_students)
{
FILE *file;
system("cls");
printf("Saving base to file!\n");
file=fopen("database.txt","wb");
fprintf(file,"%d",amount_of_students); // function also saves amount of students in base
fwrite(student,sizeof( data),amount_of_students,file);
fclose(file);
_getch();
system("cls");
}
void loading_base_from_file()
{
FILE *file;
system("cls");
printf("Reading base from file\n");
file=fopen("database.txt","rb");
if (file!= NULL) {
fscanf(file,"%d",&number_of_students);
fread(&student,sizeof( data),number_of_students,file); //number_of_students is global variable
student=(data*)malloc(number_of_students*sizeof(data));
fclose(file);
}
else
{
printf("File does not exist!.\r\n");
printf("File have to be named ""database.txt"" !!!\n");
}
_getch();
system("cls");
}
(Function saving_base_to_file takes number_of_students as input argument.)
Problem appears when i want to use my "loading_base_from_file" function
For example, when i want to save one student with student_id "123456" named "Greg" "Tesla", file contains this:
database.txt. Function saving_base_to_file also saves amount of students in base. But when i start my program again (or do it in one program run) and try do load data from file, my function "print_base" prints this:
result
I think that there is a problem with "putting" data into array, but i don't know what exactly is wrong.
Could you tell me why is this happening and how to fix it?

Your use of student indicates that it's a pointer. That means when you use the address-of operator & on the variable you get the pointer to where the variable is stored, not a pointer to the memory you allocate. The type of &student is data ** not data *.
That alone will lead to undefined behavior as you write over data somewhere in memory where it should not be, but is not the only problem. The other problem is that you allocate memory for student after you read the data from the file, making student point somewhere else completely and also making you lose the data you just read.
First allocate memory, then read into student (and not &student).
There is yet another problem too, and that is you mix text data and binary data. The initial number you read as the number of records in the file will not be correct when you read it, if the first element in the structure contains a value that can be parsed as a textual digit.
You need to fwrite the number, and fread it back.

Related

Dynamic fscanf into an array

I am trying to place some text into a structure part of my array is a array which takes part of the text.
For example my structure is:
struct animal
{
char animal_Type[11];
int age;
int numberOfLegs;
int walksPerDay;
char favoriteFood[];
};
I will then have input such as:
dog,2,4,2,biscuits,wet
cat,5,4,0,biscuits,wet,dry,whiskers
bird,1,2,0,birdseed,biscuits,bread,oats,worms,insects,crackers
I have a working solution that places all the values up to walks per day into the structure, however I want to be able to place the food items into Favorite food. I have a dynamic array for this, but i'm not sure how to read remaining text into the favoriteFood array.
The code used is:
fp = open("animals.txt","r");
struct animal *animal = malloc(sizeof(sturct animal)*3);
int i = 0;
if(fp != NULL) {
while(i < 3) {
fscanf(fp,"%s %d %d %d %s",
animal[i].animal_Type,
animal[i].age,
animal[i].numberOfLegs,
animal[i].walksPerDay,
animal[i].favoriteFood); // need to be able to enter the string of food into here
i++
}
How would I go about doing this?
First of, your struct doesn't match what you've said in the comments.
char favoriteFood[];
The above is an array of char, so couldn't possibly hold a list of favourite foods except if it were one string. And since the size of the array is unspecified, you'd not be able to fill it like you have been either. Instead what you actually want is
char **favoriteFood;
unsigned int favoriteFoodSize;
That will let you create an expanding list of strings to fit whatever data you need to accommodate.
As for reading it in, the best way would be to read the entire line in using fgets and then use something like strtok to break the line up by your separator character. First define a very large string to hold the entire line and a char * to hold each field.
char buffer[1024];
char *token;
And then to the main loop would be something like this:
while(fgets(buffer,1024,fp)) {
token=strtok(buffer,",");
strcpy(beasts[i].animal_Type,token);
token=strtok(NULL,",");
beasts[i].age = atoi(token);
/* etc... */
}
You'd need to check whether token is ever NULL to cope with the possibility of short lines and handle it accordingly. And also make sure that the string copied into animal_Type isn't longer than 10 characters...or alternative make it a char * so you can have any size of string.
For the favoriteFood, you'll need to use realloc to increase the size of it to accommodate each new food added and keep going through the string until you run out of tokens.
token=strtok(NULL,",");
if(token) {
beasts[i].favoriteFood=malloc(sizeof(char *));
beasts[i].favoriteFood[0]=strdup(token); // Need to index using 0 as favoriteFoodSize won't have a value yet
beasts[i].favoriteFoodSize=1;
token=strtok(NULL,",");
while(token) {
beasts[i].favoriteFood=realloc(beasts[i].favoriteFood,(beasts[i].favoriteFoodSize+1)*sizeof(char *));
beasts[i].favoriteFood[beasts[i].favoriteFoodSize]=strdup(token);
beasts[i].favoriteFoodSize++;
token=strtok(NULL,",");
}
}
The last food will have a \n in it as fgets keeps it in the buffer it reads, so you could use that to tell if you've finished processing all the foods (you will also need to remove it from the last food). Or if you don't have it, you know the line was longer and you'll need to read more in. But that seems unlikely based on your sample data.
And since you're doing lots of memory allocation, you should ensure that you check the values returned to make sure you've not run out of memory.

C - Getting a Debug Assertion Failed Error by reading a csv-file

i am just doing a program in C, which has to read different files in csv-format. Every file consists of two lines.
The first line describes what topics are saved, while the second lines contains the data of the topics. Every file has
6 columns. The data are information like dates, source and category. I actually wrote a program that get the path and
gives the content in one dynamical char array back, but there is always a Debug Assertion Error that crashes it all the time. My Code is:
char* readfile(char csvpath[]) {
FILE *csv;
int c;
int countcontent = 100; //counter for the length of the content array
int counter = 0; //counter of the amount of the inserted chars
char *temp; //temp = buffer
char *content = (char*)calloc(100,sizeof(char)); //content of the file
csv = fopen(csvpath,"r");
while(c = fgetc(csv) != EOF) { //while file isnt at the end
if(countcontent <= counter) {
realloc(content,100*sizeof(char));
countcontent += 100;
}
temp = (char*)calloc(20,sizeof(char));
fgets(temp,20,csv);
content = concat(content,temp); //concat is my own function and add the 2. string behind the 1.
counter+= 20;
}
fclose(csv);
return content;}
Actually i ignore that there are two different lines, cause i want to delete the first one at the end anyway, because no data is saved there. But can you help me to find the solution for this error?
Your problem is this line
realloc(content,100*sizeof(char));
The realloc function returns a pointer to the reallocated memory. Think about what would happen if the realloc call can't just resize the already allocated chunk, and has to actually allocate a completely new chunk of memory, because it can't automatically update the pointer you pass to it.
Another problem with that statement is that it doesn't matter how much more memory you need, you will always allocate 100 bytes. The size argument you provide to realloc is the new size.
Oh, and remember that realloc can fail, and return a NULL pointer. If you don't want to loose your original pointer, you need to have a temporary variable to store the returned pointer, and check it for NULL.

Easiest way to allocate "blank" block of data to .dat file

Looking for a quick way to allocate a block of data to be managed from disk. I'm allocating a block of 50 structs, and while most of the memory allocates fine, when I read it all back I get junk messages returned in some of the fields that should be blank. I assume this is me allocating the space incorrectly somehow that allows some junk from memory to leak in there.
if ((fpBin = fopen(BINARYFILE, "w+b")) == NULL)
{
printf("Could not open binary file %s.\n", BINARYFILE);
return;
}
fwrite(fpBin, sizeof(struct student), 50, fpBin); //Write entire hash table to disk
struct definition
typedef struct student
{
char firstName[20]; //name
char lastName[20];
double amount; //amount owed
char stuID[5]; //4 digit code
}student;
Is how I was taught, yet I'm still getting some junk in my data instead of it being a clean slate. So question: How do I set all fields to blank?
Answer:
student tempStu[50] = {0};
fwrite(tempStu, sizeof(struct student), BUCKETSIZE, fpBin); //Write entire hash table to disk
fwrite(fpBin, sizeof(struct student), 50, fpBin);
You're writing your file pointer, not your student structs, to disk. That first fpBin should instead be a pointer to your data. That data can be an array of 50 student structs initialized to 0, perhaps with calloc or by defining it at file scope, but it has to be somewhere. Instead, you are writing 50*sizeof(struct student) bytes from your fpBin pointer, which is undefined behavior -- you'll either crash with an access violation or you'll write junk to disk. That junk is what you're getting when you read it back.
Also, using a constant like 50 is bad practice ... it should be a variable (or manifest constant) that holds the number of students that you're writing out.
BTW, on Linux and other POSIX systems, you could allocate a block of zeroes on disk just by writing the last byte (or in some other way making the file that large).

C Array Behaviour - Global / Local / Dynamic

I am having problems updating an array globally from a while loop, as expalined below. Please note that I can only use functionality from C 95 and before. Anyhelp would be greatly appreciated! Full paste bin http://pastebin.com/ss6VgTCD
Declared at the Top of my program
int data_count, i;
float *x_values, *y_values;
float x[100],y[100];
In My main function my arrays are created using the code below:
printf("\nPlease Enter How Many Data Points You Wish To Enter: \n");
scanf("%d", &data_count);
x_values=(float*)calloc(data_count,sizeof(*x_values));
y_values=(float*)calloc(data_count,sizeof(*y_values));
if (x_values==NULL) {
printf("Error! Memory Could Not Be Allocated. ");
exit(0);
}
File read function to import previously entered data, the function is getting the correct data and displays the correct data points in my debugging line printf("%12f%12f\n", x_values[i], y_values[i]); however is only locally updating x_values and y_values as these imported data can not be seen by the rest of the program. How can I globally update the array?
void file_read(void) {
store = fopen ("j:/StoredValues.txt", "r");
if (store == NULL )
printf("\nError: Failed To Open Previous Data File - Program Will Continue Anyway\n");
else {
printf("\nSuccess: Data From Previous Run Imported\n");
i=0;
do {
fscanf ( store, "%f,%f\n", &x[i], &y[i]);
x_values = x;
y_values = y;
printf("%12f%12f\n", x_values[i], y_values[i]);
i=i+1;
} while (!feof(store));
fclose(store);
}
}
p.s. Ive only coded in C for 2 weeks so simple is nice :)
In the first code block you've allocated memory and saved the pointer to it in 'x_values'.
In the second block you change 'x_values' to point to the 'x' array. The 'x' array already has memory allocated to it for 100 floating point values.
You will no longer have a pointer to the allocated memory after the assignment.
Any data stored there is no longer accessible since you no longer have a pointer to it.
edit:
Here's a suggested replacement for the file_read() routine:
void file_read(void) {
store = fopen ("j:/StoredValues.txt", "r");
if (store == NULL )
printf("\nError: Failed To Open Previous Data File - Program Will Continue Anyway\n");
else {
printf("\nSuccess: Data From Previous Run Imported\n");
float* px;
float* py;
px = x_values;
py = y_values;
while (!feof(store))
{
fscanf ( store, "%f,%f\n", px, py);
printf("%12f%12f\n", *px, *py );
px++;
py++;
}
fclose(store);
}
}
Edit 2:
Menu choice 2 will display the content of x_values. If file_read() places the content in the x array then you can't display it using option 2. You also can't copy the content of the x array to x_values since x_values doesn't exist yet.
You need to create a storage area for your data before you try to read it in.
To do that you need to store the count of how many points are in the file.
Also consider:
The user enters 10 points and you allocate space for 10.
Then the user wants to enter new data and wants to enter 12.
You now have to free() and alloc() to get space for the extra two.
x_values = x;
y_values = y;
This causes x_values and y_values to point at the statically allocated arrays instead of the data you allocated dynamically for them. So after this assignment, the dynamic data now sits alone and isolated in your RAM as a memory leak, with no reference to it from your program.

making program read certain parts of the file

I've a file which contains names and grades of students, and I'd like to write a program which can sort their grades (like midterm 1,midterm 2) according to user choice. I wrote as far as the choice part and opening the file, yet I don't know how to make program read only certain part of the file (like only Midterm 1 grades for example) and sort them only. Here's what I've wrote so far;
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int number;
char name[30];
char surname[30];
int midterm1,midterm2,midterm3;
} Student;
int main()
{
int choice,studentnumber,midterm1,midterm2,midterm3;
char surname;
FILE *cfPtr;
struct student *name;
name = malloc( 10 * sizeof(Student));
if ((cfPtr = fopen("grades.txt", "r")) == NULL)
printf("File cannot be opened.\n");
else {
const int STUDENTSMAX = 100;
Student students[STUDENTSMAX];
int i = 0;
while (!feof(cfPtr))
{
fscanf(cfPtr, "%d%s%s%d%d%d", &students[i].number, &students[i].name,&students[i].surname, &students[i].midterm1, &students[i].midterm2, &students[i].midterm3);
printf("%4d%15s%15s%10d%10d%10d\n", students[i].number, students[i].name,students[i].surname, students[i].midterm1, students[i].midterm2, students[i].midterm3);
i++;
}
printf("What would you like to do? \n"
"1- Sort according to midterm 1\n"
"2- Sort according to midterm 2\n"
"3- Sort according to midterm 3\n"
"4- Exit\n");
scanf("%d",&choice);
while (choice != 4);{
switch (choice) {
case 1:
qsort(students,10,sizeof(int),comp);
for (i=0; i<9; i++)
printf("%4d%15s%15s%10d%10d%10d\n", students[i].number, students[i].name,students[i].surname, students[i].midterm1);
fclose(cfPtr);
}
system("PAUSE");
return 0;
}
Given what might be a somewhat free form text file (based on the shown code), it probably makes sense just to read the entire file (somewhat like you are already doing) and only use the parts that you need. If the text file has a very specific format with fixed offsets, you could seek to certain locations in the file and read a specific column value, then seek to the next offset and read the column value from the next row. But that is probably more trouble than it is worth and would not be much more efficient (if at all).
Having said that, to sort the results, you probably need the entire file anyway. For example, if you just read and sort the "midterm 1" value, then the result would just be sorted grades without any associated name and student number. So without knowing more about the goal, you might consider creating a struct that can hold a single row (student number, name, surname, midterm1, etc.). Then create an array of those and read each row into an element of the array. If you know how many rows exist up front, you can allocate the array in one chunk, otherwise you might need to reallocate it as you go to grow it.
Once you have read the entire array, you could sort based on the desired value (e.g., with qsort.
Having mentioned that, there a few problems/issues with the existing shown code:
The second printf has fewer format specifiers (%s) than parameters.
The third printf with the "What would you like to do" question is missing the closing paren.
The fprintf is incorrect; it should have a file handle as the first parameter. I suspect, though, that it was maybe meant to be printf?
The final while loop has an extraneous semicolon (;) following its closing paren, which means that it has an empty body rather than the apparently intended printf and switch statement.
The switch statement is a bit odd as written. I assume that is the "unfinished" part. But including the fclose in it seems strange. It should probably be at the end of the main else.
Using system("PAUSE"); is maybe not the best choice. Perhaps using getch would make more sense to pause for input.
Edit Here is some additional information in response to your comment asking for more details. This sounds like homework to me, so it doesn't seem right just to give the answer. But here is one way to do it:
Define a struct with the 6 items that are in the file (basically put in the 6 variables that you currently have defined as local variables).
Declare a local variable (e.g., grades) as a pointer to struct that you defined.
Use malloc to allocate memory and assign it to the pointer just mentioned. The amount of memory is perhaps the trickiest part of this whole thing. The size parameter to malloc will be something like numRecs * sizeof( yourstruct ). The question is what numRecs should be. If this is an assignment and you were told how many records there would be (a maximum), then just use that. If, though, it is "unknown", then there are a couple of ways of dealing with that. One is to just guess at a number (e.g., 100) and then while reading them in a loop use realloc if you exceed 100. The other alternative (probably less efficient) would be to use two loops - read through them once without storing them but just count them and then allocate the known size and read them again. I would use the realloc version.
Replace the use of the local variables with the array (that was malloced). For example, instead of studentnumber you would use grades[arraypos].studentnumber. While you read them in, keep a counter of how many there are. You can then use qsort to sort the array.
Edit 2
Your struct definition looks correct except that the FILE *cfPtr; member should not be in it. It should still be a local variable.
For ease of use, you can define the struct as typedef struct { ... } Student;. That way you can just use Student instead of struct Student in the code. Note that I capitalized the name (personal preference in naming to make it not look like a variable name).
For the malloc, you are close. But as written, it is allocating space for a single record. You would need something like this: name = malloc( 50 * sizeof( struct student )); (or malloc( 50 * sizeof( Student )); if you change it to use the typedef. That assumes there would not be 50 or fewer records to read from the file.

Resources