I've a newbie question regarding freeing memory allocated for an array of struct.
This is the code:
typedef struct {
char code[6];
char name[31];
char cname[31];
int anno;
} cliente;
cliente *readcostumers(char*filename,int * dim) {
int i;
cliente *d;
cliente temp;
FILE*fp;
fp=fopen(filename,"r");
*dim=0;
//count the number of lines
while(fscanf(fp,"%s %s %s %d", temp.code, temp.name, temp.cname,&(temp.anno))==4)
(*dim)++;
rewind(fp);
//allocate "dim" struct
int ss = sizeof(cliente);
d = (cliente*)malloc(ss * (*dim));
cliente *currCli = d;
//assign lines to struct
for(i=0; i<*dim; i++) {
fscanf(fp,"%s %s %s %d",currCli->code, currCli->name, currCli->cname, &(currCli->anno));
currCli = currCli + ss;
}
fclose(fp);
return d;
}
This piece of code basically reads a text file, with any number of lines, formatted with a specific pattern and assign the contents to an array of strcut cliente.
This seems to work fine, except when i free the memory previous allocated:
int main () {
int x,i;
cliente *f = readcostumers("c:/temp/clienti.txt",&x);
int len = sizeof(cliente);
for(i=0; i<x; i++) {
printf("\n%s %s %s %d",(f + len*i)->code, (f + len*i)->name,
(f + len*i)->cname, (f + len*i)->anno);
}
free(f);
}
The last statement free(f) causes a SIGTRAP exception, altough the values printed are correct read from file.
The file content is this:
A3789 Paolo Rossi 2001
X478D Marcantonio Bianchi 2004
Where is the mistake?
When you increment currCli in readcostumers you should use 1 and in main you shouldn't multiply the index with len. These are taken care of by the language. These two errors are compensating each other, but in the mean time you are accessing outside the allocated memory and most likely overwriting internal administration of the heap allocation algorithms. Ultimately resulting in a crash in free.
Look into the valgrind tool as it finds these kind of errors flawlessly.
What are you doing here?
currCli = currCli + ss;
You should be incrementing the pointer by 1, not by the size of an element in the array you allocated. (The language automatically scales pointer additions by the size of the object pointed to.) This leads to writing outside the allocated area, which in turn leads to corrupted memory, and SIGTRAP or core dump or other similar problems.
Related
I am new to C and I have been stuck on this code for the whole moring.
It compiles without a problem, but fails when executed.
If you have any idea that would help me solve this, please leave me a comment. Any comment would be greatly appreciated.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct phonebook {
char name[20];
char phoneNum[20];
} Phonebook;
int bookSize=1;
void load(Phonebook **book);
void insert(Phonebook **book);
void delete(Phonebook **book);
void search(Phonebook *book);
void print(Phonebook *book);
void save(Phonebook *book);
int main(void) {
Phonebook *book = (Phonebook *)calloc(sizeof(Phonebook), bookSize);
load(&book);
int menuInput=0;
while(menuInput != 5) {
puts("***** MENU *****");
puts("1. Insert");
puts("2. Delete");
puts("3. Search");
puts("4. Print All");
puts("5. Exit");
printf(">> ");
scanf("%d", &menuInput);
switch(menuInput) {
case 1 : insert(&book); break;
case 2 : delete(&book); break;
case 3 : search(book); break;
case 4 : print(book); break;
case 5 : break;
default : puts("enter correct command"); break;
}
}
save(book);
free(book);
puts("\nexit\n");
return 0;
}
void load(Phonebook **book) {
FILE *fp = fopen("phonebook.txt", "rt");
if(fp == NULL) {
FILE *fp = fopen("phonebook.txt", "wt");
fclose(fp);
puts("Welcome! It looks like you don't have an existing phonebook.");
puts("A new phonebook has been created.\n");
return;
}
else {
char temp[20];
int i=0;
while(fscanf(fp, "%s", temp) != EOF) {
strcpy(book[i]->name, temp);
fscanf(fp, "%s", temp);
strcpy(book[i]->phoneNum, temp);
i++;
bookSize++;
*book = (Phonebook *)realloc(*book, sizeof(Phonebook) * (bookSize));
}
fclose(fp);
printf("Loaded %d contacts\n", bookSize-1);
}
}
void insert(Phonebook **book) {
puts("\nCreate a new contact");
getchar();
char temp[20];
printf("Name : ");
fgets(temp, 20, stdin);
//temp[strlen(temp)-1]=0;
strcpy(book[bookSize-1]->name, temp);
//fgets(book[bookSize-2]->name, 20, stdin);
//book[bookSize-2]->name[strlen(book[bookSize-2]->name)-1]=0;
printf("Phone : ");
fgets(temp, 20, stdin);
//temp[strlen(temp)-1]=0;
strcpy(book[bookSize-1]->phoneNum, temp);
//fgets(book[bookSize-2]->phoneNum, 20, stdin);
//book[bookSize-2]->phoneNum[strlen(book[bookSize-2]->phoneNum)-1]=0;
puts("Done!\n");
bookSize++;
*book = (Phonebook *)realloc(*book, sizeof(Phonebook) * bookSize);
}
void delete(Phonebook **book) {}
void search(Phonebook *book) {}
void print(Phonebook *book) {
if(bookSize == 1) {
puts("\nempty\n");
return;
}
puts("");
for(int i=0; i<bookSize-1; i++) {
printf("Name : %-10s Phone : %s\n", book[i].name, book[i].phoneNum);
}
puts("");
}
void save(Phonebook *book) {
FILE *fp = fopen("phonebook.txt", "wt");
for(int i=0; i<bookSize-1; i++) {
fprintf(fp, "%s\n%s\n", book[i].name, book[i].phoneNum);
}
fclose(fp);
printf("\nSaved %d contacts", bookSize-1);
}
Segmentation fault (core dumped)
** sorry for removing parts of the code I thought was 'irrelevant'! I have added the whole code to the post. Thanks!
As your other answer indicates, you are tripping over the details of the double indirection.
You are maintaining your phone book as an array of structures. In main, variable book is a pointer to the first structure in that array. The second will immediately follow it in memory, and the third will immediately follow that, etc.. That's all perfectly fine.
Both insert() and load() accept as a parameter a pointer to the pointer to the first book. This also is right and proper, because these methods reallocate the memory for the array. Reallocation is not necessarily done in place -- the new space may be in a different location than the old. The original pointer passed into realloc must be considered invalid after the call, and the return value used in its place (supposing the call succeeds). You handle this correctly, too, updating main's pointer through the pointer argument:
*book = (Phonebook *)realloc(*book, sizeof(Phonebook) * (bookSize));
But your attempts to write phone book entries into the allocated space is incorrect. For example, in load(), this:
strcpy(book[i]->name, temp);
tries to access the ith Phonebook * in the array of pointers to which book points, and to write to the name member of the Phonebook to which it points. But there is only ever one Phonebook *, not an array of them. You are allocating and reallocating space for the Phonebooks to which it points.
Here's a crude diagram:
Actual layout:
[Phonebook **] ----> [Phonebook *] ----> [ Phonebook, Phonebook, Phonebook ... ]
Being accessed as if it were:
[Phonebook **] ----> [Phonebook *, Phonebook *, Phonebook *, ...]
| | |
V | |
[Phonebook] V |
[Phonebook] V
[Phonebook]
Solution:
Just as you assign the allocated pointer to *book, not to book, it is *book to which you should be applying the indexing operator:
strcpy((*book)[i].name, temp);
And since it's an array of Phonebooks, not an array of pointers to them, you use the direct member access operator (.), as shown, not the indirect access operator.
Beware, however, that you use the same name, book, in different functions to designate pointers with different degrees of indirection. Thus, whereas the above would be correct in load() and insert(), it would be wrong in main() and some of the other functions.
tl;dr: insert(&book) should just be insert(book), and define this to be the address you get from allocating memory in the heap for storing the address you get from calloc.
You define your argument for insert() as **book, and when you get a *book from your calloc() call, you reasonably "add on another *" with the address operator &. The catch is that the address of *book that you got from your calloc call is a location on the call stack of your main() function. So, when the argument of strcpy() goes to dereference this address with the array index notation, it attempts to get the value located at the pointer that's on your call stack + bookSize - 1. This is already in undefined behavior territory, since the stack isn't supposed to store memory dynamically, but you're getting the segfault because the stack is at the top of the memory layout (high address area), so adding a large enough value to the dereferenced value of book puts you in an illegal memory access zone.
I'm writing a homework program in C. The program should take records from an input file and write those record to an output file. It seems like there is something wrong with the print_to_file function. I keep getting segmentation fault 11. Please help. My code is as below.
#include <stdio.h>
#include <stdlib.h>
typedef struct car { // create a struct type Car
char *license_plate;
int parking_spot;
int num_tickets;
int time_left;
} Car;
#define LICENSEPLATELENGTH 10
Car* import_cars(char *filename, int numCars);
void print_to_file(char* filename, Car* garage, int numCars);
int main(int argc, char * argv[]) {
if(argc != 4)
printf("Incorrect input.\n");
else {
int number = atoi(argv[1]);
Car* parked_car = (Car*)malloc(sizeof(Car) * number);
parked_car = import_cars(argv[2], number);
print_to_file(argv[3], parked_car, number);
free(parked_car);
}
return 0;
}
Car* import_cars(char* filename, int numCars)
{
Car* inCar = (Car*)malloc(sizeof(Car) * numCars);
inCar->license_plate = (char*)malloc(sizeof(char) * 8);
//Question: How do I do if I the plate length is varied. How to malloc space to it?
FILE* inFilePtr;
if((inFilePtr = fopen(filename, "r")) == NULL)
printf("Error! Unable to open file %s. Check again.\n", *filename);
else
{
int i = 0;
fscanf(inFilePtr, "%s", inCar[i].license_plate);
fscanf(inFilePtr, "%d%d%d", inCar[i].parking_spot, inCar[i].num_tickets, inCar[i].time_left);
printf("%s %d %d %d \n", inCar[i].license_plate, inCar[i].parking_spot, inCar[i].num_tickets, inCar[i].time_left);
for(i = 1; i < numCars; i++)
{
fscanf(inFilePtr, "%s", inCar[i].license_plate);
fscanf(inFilePtr, "%d%d%d", inCar[i].parking_spot, inCar[i].num_tickets, inCar[i].time_left);
printf("%s %d %d %d \n", inCar[i].license_plate, inCar[i].parking_spot, inCar[i].num_tickets, inCar[i].time_left);
}
}
fclose(inFilePtr);
return(inCar);
//free(inCar.license_plate); `
//Question: Do I need to free space here would it remove the value
//stored in the variable which passed to main?
}
void print_to_file(char* filename, Car* garage, int numCars) {
FILE* outFilePtr;
if((outFilePtr = fopen(filename, "w+")) == NULL){
printf("Error! Cannot Open File %s!", *filename);
printf("here\n");
} else {
int i = 0;
for(i = 0; i < numCars; i++) {
printf("%s\n%d %d %d\n", garage[i].license_plate, garage[i].parking_spot, garage[i].num_tickets, garage[i].time_left);
fprintf(outFilePtr, "%s\n%d %d %d\n", garage[i].license_plate, garage[i].parking_spot, garage[i].num_tickets, garage[i].time_left);
}
}
fclose(outFilePtr);
}
This is my input command.
./a.out 6 garage.txt output.txt
Here is what print in my terminal.
fi590dz 20 2 25
57fjgmc 8 0 55
7dkgjgu 25 1 15
f9e829d 1 2 60
4jgfd81 12 2 10
Segmentation fault: 11
By the way, I'm pretty new in programming and really bad with debugging. Could you give me some tips of how to debug or any debugging tools? I use a mac so gdb doesn't work.
Not a complete answer, because it’s a homework problem and you want to figure it out yourself, but here are some hints.
First, you really want to learn how to run your program in a debugger and get it to tell you which line crashed the program, and on which data.
Second, make sure you initialize the pointers for every element of the array before you try to read or write them.
Third, you’ll save yourself a lot of trouble if you initialize all your dynamic and local variables to zeroes, not garbage. It will make a lot of bugs reproducible, make a lot of bugs crash immediately instead of corrupting memory, and also make it obvious when you debug that you’re using uninitialized data.
Therefore, I suggest you get in the habit of allocating your dynamic arrays with calloc(), not malloc().
The problem lies within your parked_car = import_cars(argv[2], number); and Car* import_cars(char* filename, int numCars);functions.
Indeed in Car* import_cars(char* filename, int numCars); you are doing this:
Car inCar;
inCar.license_plate = (char*)malloc(sizeof(char) * 8);
So you are creating a local variable that is not accessible outside of the function (many different things can happen to the memory after the end of the function).
So when you do: parked_car = import_cars(argv[2], number); you are assigning to parked_car a freed variable.
A solution is to simply use the parked_caras an argument of your import_cars() function. All modifications made within the function will still be valid after it returns. So you should have:
void import_cars(char* filename, int numCars, Car* car);
For everyone who met the issue here, I found the problem in my program. The problem is that I didn't allocate space for each of the license_plate pointer in the structure. So my way to solve it is add a line as below in the for loop of the import_cars function.
inCar[i].license_plate = (char*)malloc(sizeof(char) * LICENSEPLATELENGTH);
I am fairly new to the C language and allocating memory/using pointers in general. Anyway, I was experimenting with reading a file, putting those values in a struct, etc. I know exactly what I want to do and of course the program runs but the output is incorrect and some kind of jumbled numbers and letters.
There is a text file with new information for each line. Each line represents one object.
This is how a line in the file might look:
meat sirloin 6.55 8 8.50 4
Overall I want to be able to store all of my PRODUCT objects in an array (so I have an array of structs). So I attempted to allocate memory with a pointer, use a line count, and then send the pointer to a function called read. In read I add each struct to the array via a pointer. The program doesn't crash, the output is just not correct and I have no clue why not. It's prob something with pointers. If anyone could help me I would really appreciate it. Any help at all would be great.
//prototype
void read(pointerToArr);
typedef struct
{
char supType[15];
char prodName[15];
double wholePrice;
int quantWhole;
double retPrice;
int retProdQuantity;
}PRODUCT;
FILE *fr;
int lineCount = 0;
int main()
{
PRODUCT *ptr;
int i;
char check[50];
fr = fopen("ttt.txt", "r");
while(fgets(check, sizeof(check), fr)!= NULL)
{
if(check[0] != '\n')
{
lineCount++;
}
}
// allocate memory for array based on line count.
PRODUCT prodContainter[lineCount];
ptr = (PRODUCT*)malloc(sizeof(PRODUCT)*lineCount);
ptr = prodContainter;
read(ptr);
//print after adding to array via pointer from other
//function. this was a test.
for(i = 0; i < lineCount; i++)
{
printf("%s ", prodContainter[i].supType);
printf("%s ", prodContainter[i].prodName);
printf("%f ", prodContainter[i].wholePrice);
printf("%d ", prodContainter[i].quantWhole);
printf("%f ", prodContainter[i].retPrice);
printf("%d\n\n", prodContainter[i].retProdQuantity);
}
return 0;
}
void read(PRODUCT *pointerToArr)
{
// objective in this method is to read in data from the file, create an object for every line and
// then use the pointer array to add those objects to prodConstainer up above.
char supplyName[15];
char productName[15];
double wholeP = 0;
int quantityWhole = 0;
double retailPrice = 0;
int retailProductQuant = 0;
while(fscanf(fr, "%s %s %lf %d %lf %d", supplyName, productName, &wholeP, &quantityWhole, &retailPrice, &retailProductQuant) == 6)
{
PRODUCT record;
int i;
strcpy(record.supType, supplyName);
strcpy(record.prodName, productName);
record.wholePrice = wholeP;
record.quantWhole = quantityWhole;
record.retPrice = retailPrice;
record.retProdQuantity = retailProductQuant;
for(i = 0; i < lineCount; i++)
{
pointerToArr[i] = record;
}
}
fclose(fr);
}
You never rewind the file, so all the reading after you count the number of lines fails.
What you're printing is just what happens to be in memory.
There are many ways to fix this, of course.
Rewind the file, using rewind()
Close the file and let your read() function (whose name collides with a POSIX standard function, btw) re-open the file. This would also involve removing the scary global variable fr.
Re-structure so you never count the number of lines, by just reading and letting the ptr array grow as necessary (see realloc()).
Also, you should really avoid casting the return value of malloc() in C. This:
ptr = (PRODUCT*)malloc(sizeof(PRODUCT)*lineCount);
is better written as:
ptr = malloc(lineCount * sizeof *ptr);
This does away with the cast, and also uses sizeof on a value of the type pointed at to automatically compute the proper number of bytes to allocate.
I am having trouble with a struct array. I need to read in a text file line by line, and compare the values side by side. For example "Mama" would return 2 ma , 1 am because you have ma- am- ma. I have a struct:
typedef struct{
char first, second;
int count;
} pair;
I need to create an array of structs for the entire string, and then compare those structs. We also were introduced to memory allocation so we have to do it for any size file. That is where my trouble is really coming in. How do I reallocate the memory properly for an array of structs? This is my main as of now (doesn't compile, has errors obviously having trouble with this).
int main(int argc, char *argv[]){
//allocate memory for struct
pair *p = (pair*) malloc(sizeof(pair));
//if memory allocated
if(p != NULL){
//Attempt to open io files
for(int i = 1; i<= argc; i++){
FILE * fileIn = fopen(argv[i],"r");
if(fileIn != NULL){
//Read in file to string
char lineString[137];
while(fgets(lineString,137,fileIn) != NULL){
//Need to reallocate here, sizeof returning error on following line
//having trouble seeing how much memory I need
pair *realloc(pair *p, sizeof(pair)+strlen(linestring));
int structPos = 0;
for(i = 0; i<strlen(lineString)-1; i++){
for(int j = 1; j<strlen(lineSTring);j++){
p[structPos]->first = lineString[i];
p[structPos]->last = lineString[j];
structPos++;
}
}
}
}
}
}
else{
printf("pair pointer length is null\n");
}
}
I am happy to change things around obviously if there is a better method for this. I HAVE to use the above struct, have to have an array of structs, and have to work with memory allocation. Those are the only restrictions.
Allocating memory for an array of struct is as simple as allocating for one struct:
pair *array = malloc(sizeof(pair) * count);
Then you can access each item by subscribing "array":
array[0] => first item
array[1] => second item
etc
Regarding the realloc part, instead of:
pair *realloc(pair *p, sizeof(pair)+strlen(linestring));
(which is not syntactically valid, looks like a mix of realloc function prototype and its invocation at the same time), you should use:
p=realloc(p,[new size]);
In fact, you should use a different variable to store the result of realloc, since in case of memory allocation failure, it would return NULL while still leaving the already allocated memory (and then you would have lost its position in memory). But on most Unix systems, when doing casual processing (not some heavy duty task), reaching the point where malloc/realloc returns NULL is somehow a rare case (you must have exhausted all virtual free memory). Still it's better to write:
pair*newp=realloc(p,[new size]);
if(newp != NULL) p=newp;
else { ... last resort error handling, screaming for help ... }
So if I get this right you're counting how many times pairs of characters occur? Why all the mucking about with nested loops and using that pair struct when you can just keep a frequency table in a 64KB array, which is much simpler and orders of magnitude faster.
Here's roughly what I would do (SPOILER ALERT: especially if this is homework, please don't just copy/paste):
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
void count_frequencies(size_t* freq_tbl, FILE* pFile)
{
int first, second;
first = fgetc(pFile);
while( (second = fgetc(pFile)) != EOF)
{
/* Only consider printable characters */
if(isprint(first) && isprint(second))
++freq_tbl[(first << 8) | second];
/* Proceed to next character */
first = second;
}
}
int main(int argc, char*argv[])
{
size_t* freq_tbl = calloc(1 << 16, sizeof(size_t));;
FILE* pFile;
size_t i;
/* Handle some I/O errors */
if(argc < 2)
{
perror ("No file given");
return EXIT_FAILURE;
}
if(! (pFile = fopen(argv[1],"r")))
{
perror ("Error opening file");
return EXIT_FAILURE;
}
if(feof(pFile))
{
perror ("Empty file");
return EXIT_FAILURE;
}
count_frequencies(freq_tbl, pFile);
/* Print frequencies */
for(i = 0; i <= 0xffff; ++i)
if(freq_tbl[i] > 0)
printf("%c%c : %d\n", (char) (i >> 8), (char) (i & 0xff), freq_tbl[i]);
free(freq_tbl);
return EXIT_SUCCESS;
}
Sorry for the bit operations and hex notation. I just happen to like them in such a context of char tables, but they can be replaced with multiplications and additions, etc for clarity.
Hmm not sure if the title of this makes any sense.
I'm creating a program that reads information from the file marks.txt (This is homework by the way so please bear with me I'm still wandering in the dark a bit :) )
marks.txt:
U08006 3 30 40 30
12000001 55 42 60
12000002 37 45 40
12000003 58 0 24
12000004 74 67 80
12000005 61 50 38
12000006 70 45 58
99999999
the first line is a module code followed by number of assignments and weighting of each assignment. The other lines are student numbers followed by marks for each assignment. And finally the end of the list is indicated by the student number 99999999.
EDIT: the program should eventually output the number of students and the average and standard deviation, for each assignment and overall module mark. Also I've been instructed to use an array of structures.
I have stored the module information in a struct (module), but when it comes to storing the student information I am really lost. I need an array of structs for the students but the problem is that I don't know how many students are in the file (well obviously I do right now but if I want my program to be able to read the file even though the number of students changes having a fixed size array isn't helpful).
I was thinking maybe I could count the number of lines in the file between the first and the last. and then use malloc to allocate memory for the array? am I on the right track here. so something like student = malloc(sizeof(student???) * number of lines)
this is really confusing me! dealing with pointers arrays structs and files all at once is really getting me all muddled up and this is due during my next class so can't ask my tutor for help. So any tips would be greatly appreciated.
my code so far in all it's "work in progress"ness:
EDIT: ok so I've pretty much finished the program i.e. it does what it is suppose to but I'm still struggling with the whole malloc thing. for my student struct I want an array of structs and I want to allocate the memory after I determine the number of students but I'm not sure how I'm suppose to do this?! this whole pointer to array of structs I'm not quite getting it so the following code doesn't work. (if I have an array of struct student of predetermined size everything is peachy)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int studentCount();
void getModuleDetails(FILE *fileptr);
void getStudentDetails(FILE *fileptr, int classSize);
void overallMarks(int classSize);
void cleanUp(FILE *fileptr, int classSize);
void printModuleStats(int classSize);
struct ModuleSpec {
char moduleCode[7];
int numOfAssign; /*number of assignments*/
int *assignWeighting; /*assignment weighting*/
} module; /*declare struct with variable name module*/
struct StudentMarks {
int studentNumber;
int *assignMark; //assignment mark
int moduleMark; //overall mark for module
}*student;
/*******************************************************************************************/
int main() {
int i; /*index*/
FILE *pFile;
int studentCounter = studentCount(); //count number of students in class
student = malloc(sizeof(student)*studentCounter); /*allocate memory for array of student structs*/
pFile = fopen("marks.txt", "r"); /*open file marks.txt for reading*/
if(pFile==NULL){ /*check if file was opened successfully*/
printf("File could not be opened!");
exit(EXIT_FAILURE);
}
getModuleDetails(pFile); //get module details from pFile and store in struct module
getStudentDetails(pFile, studentCounter); //get student details from pFile and store in student array fo structs
overallMarks(studentCounter); //calculate and print overall marks of students
printModuleStats(studentCounter);
cleanUp(pFile, studentCounter);
return 0;
}
/*****************************************************************************************/
int studentCount(){
FILE *pFile;
int temp;
int studentCount = 0;
pFile = fopen("marks.txt", "r"); /*open file marks.txt for reading*/
if(pFile==NULL){ /*if file can't be opened*/
printf("File could not be opened!");
exit(EXIT_FAILURE);
}
do{
temp = fgetc(pFile);
if(temp == '\n'){
studentCount++;
}
}while(temp != EOF);
studentCount -=2; /*subtract first and last lines to count only students*/
//printf("The number of students on this module is: %d\n", studentCount);
fclose(pFile);
return studentCount;
}
/*******************************************************************************************/
void getModuleDetails(FILE *fileptr){
int i;
int sumWeighting = 0;
fscanf(fileptr, "%s %d", module.moduleCode, &module.numOfAssign);
printf("Module code: %s\n", module.moduleCode);
printf("Number of assignments: %d\n", module.numOfAssign);
module.assignWeighting = malloc(module.numOfAssign * sizeof(int)); /*Allocate memory to hold assignment weightings*/
if (module.assignWeighting == NULL) { /*check if memory allocation was successful*/
printf("Memory allocation failed.");
exit(EXIT_FAILURE);
}
/*get weighting for each assignment and store in module.assignWeighting*/
for(i=0;i<module.numOfAssign;i++){
fscanf(fileptr, "%d", &module.assignWeighting[i]);
//printf("module %d %d\n", i+1, module.assignWeighting[i]);
sumWeighting += module.assignWeighting[i];
}
/*check if sum of weighting equals 100*/
if(sumWeighting != 100){
printf("Error: Sum of weighting = %d\n Expected sum: 100\n", sumWeighting);
}
}
/*********************************************************************************************/
void getStudentDetails(FILE *fileptr, int classSize){
int i;
int j;
for(i=0;i<classSize;i++){
student[i].assignMark = (int *)malloc(sizeof(int) * module.numOfAssign);
if(student[i].assignMark == NULL) { //check if memory allocation was successful
printf("Memory allocation failed.");
exit(EXIT_FAILURE);
}
fscanf(fileptr, "%d", &student[i].studentNumber);
/*get student assignment marks*/
for(j=0;j<module.numOfAssign;j++){
fscanf(fileptr, "%d", &student[i].assignMark[j]);
//printf("mark for assignment %d: %d\n", j+1, student[i].assignMark[j] );
/*check if mark is within range 0 to 100*/
if(student[i].assignMark[j]<0 || student[i].assignMark[j]>100){
printf("Error: Assignment mark is not within the range 0 to 100");
}
}
}
}
/************************************************************************************************/
void overallMarks(int classSize){
int i;
int j;
float temp;
for(i=0;i<classSize;i++){
printf("Overall mark for student %d: \n", student[i].studentNumber);
for(j=0;j<module.numOfAssign;j++){
temp += (float)(module.assignWeighting[j] * student[i].assignMark[j]) / 100;
}
student[i].moduleMark = temp + 0.5; /*add 0.5 for rounding as converting float to int rounds down*/
printf("%d%%", student[i].moduleMark);
if(student[i].moduleMark<25){
printf(" is a FAIL\n");
}
else if(student[i].moduleMark<40){
printf(" is a RESIT\n");
}
else if(student[i].moduleMark<55){
printf(" is a PASS\n");
}
else if(student[i].moduleMark<70){
printf(" is a MERIT\n");
}
else if(student[i].moduleMark<100){
printf(" is a DISTINCTION\n");
}
temp = 0;
}
}
/***********************************************************************************************/
void printModuleStats(int classSize){
int i;
int j;
float averageDevMarks;
float stdDevMarks;
float averageDevModule;
float stdDevModule;
printf("\nModule Statistics for %s\n", module.moduleCode);
printf("\nNumber of students: %d\n", classSize);
/*assignments*/
for(i=0;i<module.numOfAssign;i++){
printf("\nAssignment %d:\n", i+1);
for(j=0;j<classSize;j++){
averageDevMarks += student[j].assignMark[i];
}
averageDevMarks /= classSize;
printf("Average deviation: %f\n", averageDevMarks);
for(j=0;j<classSize;j++){
stdDevMarks += (student[j].assignMark[i] - averageDevMarks)*(student[j].assignMark[i] - averageDevMarks);
}
stdDevMarks = sqrt(stdDevMarks/classSize);
printf("Standard deviation: %f\n", stdDevMarks);
stdDevMarks = 0;
averageDevMarks = 0;
}
/*modules*/
for(i=0;i<classSize;i++){
averageDevModule += student[i].moduleMark;
}
averageDevModule /= classSize;
printf("\nAverage deviation for module mark: %f\n", averageDevModule);
for(i=0;i<classSize;i++){
stdDevModule += (student[i].moduleMark - averageDevModule)*(student[i].moduleMark - averageDevModule);
}
stdDevModule = sqrt(stdDevModule/classSize);
printf("Standard deviation for module mark: %f\n", stdDevModule);
}
/************************************************************************************************/
void cleanUp(FILE *fileptr, int classSize){
int i;
fclose(fileptr); /*close file*/
/*free previously allocated memory*/
free(student);
free(module.assignWeighting);
for(i=0;i<classSize;i++){
free(student[i].assignMark);
}
}
You can either use a linked list instead of the array as Luchian Grigore suggests, or you can use dynamically allocated array of structures, or you can use a dynamically allocated array of pointers to dynamically allocated structures. The advantage of the latter is that there is less copying to do when the array is allocated; the disadvantage is that there are more memory allocations to release.
How does this translate into code? This is an outline of the 'dynamically allocated array of pointers to dynamically allocated structures'. I hypothesize a routine to read a single student's information; it will allocate a structure with the right number of marks too. Basically, it reads a line from the file (spotting EOF and returning a null pointer when the sentinel value is read). For a student, it allocates a student mark structure, and an array of integers of the right size. It parses the line for the student number and each grade. It can report an error if there are marks missing. It returns the pointer to the allocated student. I assume there are two separate allocations — one for the student marks structure and a separate one for the array of marks.
typedef struct StudentMarks Marks;
static Marks *read_student_marks(FILE *fp, int num_grades);
Marks **marks = 0; /* Array of pointers to student marks */
size_t num_marks = 0; /* Number of marks in use */
size_t max_marks = 0; /* Number of marks allocated */
Marks *student;
while ((student = read_student_marks(fp, num_grades)) != 0)
{
assert(num_marks <= max_marks);
if (num_marks == max_marks)
{
/* Not enough space left - allocate more */
size_t new_size = max_marks * 2 + 2;
Marks **new_marks = realloc(marks, new_size * sizeof(*marks));
if (new_marks == 0)
...handle out of memory error...
...NB: you still have the original array to work with...
marks = new_marks;
max_marks = new_size;
}
marks[num_marks++] = student;
}
This starts with a small allocation (2 entries), so that the reallocation code is exercised. I've also exploited the fact that if you pass a NULL pointer to realloc(), it will allocate new memory. An alternative version would use malloc() for the initial allocation and realloc() thereafter:
size_t num_marks = 0;
size_t max_marks = 2;
Marks **marks = malloc(max_marks * sizeof(*marks));
if (marks == 0)
...handle out of memory condition...
while ((students = ...
If you're worried about having allocated too much space at the end of the loop, you can release the surplus with:
marks = realloc(marks, num_marks * sizeof(*marks));
max_marks = num_marks;
The release code is:
for (i = 0; i < num_marks; i++)
{
free(marks[i]->assignMark);
free(marks[i]);
}
free(marks);
marks = 0;
num_marks = 0;
max_marks = 0;
If you want to allocate the array of student marks, you need to decide how the student reader function is going to work. You can use the design outlined above; you'd copy the returned structure into the allocated array. You then release just the marks structure (but not the array of marks; that is still in use). You can grow the array in very much the same way as I did above. The release code is different (simpler), though:
for (i = 0; i < num_marks; i++)
free(marks[i]->assignMarks);
free(marks);
marks = 0;
num_marks = 0;
max_marks = 0;
ASCII art to the rescue — maybe...
The first code assumes you get a pointer to a set of marks for a student from the read_student_marks() function. It keeps an array of pointers to those entries:
+----------+ +--------------------+
| marks[0] |-------->| marks for 12000001 |
+----------+ +--------------------+ +--------------------+
| marks[1] |------------------------------------>| marks for 12000002 |
+----------+ +--------------------+ +--------------------+
| marks[2] |-------->| marks for 12000003 |
+----------+ +--------------------+ +--------------------+
| marks[3] |------------------------------------>| marks for 12000004 |
+----------+ +--------------------+
Notice how the marks array is contiguous, but the marks for each student are separately allocated. The code periodically reallocates the marks array when it needs to grow, but does not move the individual marks for each student.
The alternative scheme outlined is like this:
+--------------------+
| marks for 12000001 |
+--------------------+
| marks for 12000002 |
+--------------------+
| marks for 12000003 |
+--------------------+
| marks for 12000004 |
+--------------------+
Here you would be reallocating all the memory, copying the complete marks structures around. At some levels, this is simpler than the scheme I outlined first.
Complicating this discussion is the fact that the 'marks for' structures are not as simple as I showed:
+--------------------------+
| studentNumber: 12000001 | +------+------+------+
| assignMark: *--------=----------->| 52 | 45 | 60 |
| moduleMark: 92 | +------+------+------+
+--------------------------+
If you have a C99 compiler, you could use a stucture with a 'flexible array member' for the last element of the structure; this would hold the marks:
struct StudentMarks
{
int studentNumber;
int moduleMark;
int assignMark[]; // Flexible array member
}
You would allocate this using a notation such as:
struct StudentMarks *marks = malloc(sizeof(*marks) +
num_assignments * sizeof(marks->assignMark[0]));
This allocates the student mark structure and the array in a single memory allocation. However, you cannot have an array of structures with a flexible array member; you can only use an array of pointers to a structure with a flexible array member (getting you back to the first code I showed).
In case you haven't already gathered, there is more than one way to do it — which is the motto of Perl (aka TMTOWTDI), but applies well here.
As some advice, I recommend drawing diagrams of pointers similar to those I made to help you understand what you are doing. After a while, they'll cease to be necessary, but diagrams can be a great help while you need them.
Pre-reading the number of students would require two passes through the file.
A cleaner solution would be to implement a linked list, and add to it each time you read a new student.
Given some of the constraints on how you can write the program, here is an outline of one way to do it:
Read the module information, and put values into a module_struct which has the module_name and number_of_assignments (this does not need to be malloc'd)
malloc enough space for an array to hold each assignment_weighting, then put the weightings into that.
Scan the file, counting the number_of_students.
malloc enough space for an array of student_structs, number_of_students long
for each entry in the array of student_structs, malloc enough space to hold the assignment_marks, and point a field in the student_struct entry at that assignment_marks array
There should now be enough space to hold all of the values in the file
rewind the file to the start, either using fseek, or fclose and fopen it again.
scan past the module header
read each student row, assigning values into an entry of student_structs, and its assignment_marks
Do all of the calculations on the array of student_structs, and print the answers.
Is that enough information to get you moving?