C++ - printing objects in statistically allocated array causes segmentation fault - c++

So I'm creating a program that implements several classes representing a school, and its students and courses. I'm getting a segmentation fault when I try to prints out all the Taken objects in the studentCoursePairs[] array which represents Student objects taking a particular Course. I think my segmentation fault comes from the addTaken() function in School.cc where its job is to find the student object and course object with the given student number and course id, and then creates a new Taken object with the found student and course objects as well as a grade. I then try to add this new object to the back of the Taken collection which is studentCoursePairs.
When I comment out studentCoursePairs[i]->print() the segmentation fault goes away. I'm not exactly sure what I'm doing wrong and would appreciate some help.
I'm not sure if the other classes besides School.cc are needed but I included them anyways to help with understanding.
School.cc:
#include <iostream>
#include <iomanip>
using namespace std;
#include <string.h>
#include "School.h"
School::School(string s1) : name(s1){
numTaken = 0;
}
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
Additional files:
StudentCollection.cc
bool StudentCollection::find(string num, Student** s){
for(int i = 0; i < size; ++i){
if(students[i]->getNumber() == num){ //find student number
*s = students[i];
}
}
}
CoursesCollection.cc
bool CoursesCollection::find(int id, Course** c){
for(int i = 0; i < numCourses; ++i){
if(courses[i]->getId() == id){ //find course id
*c = courses[i];
}
}
}
I also have a Student class and Course class which just declare and initializes information like the name, program, gpa of a student as well as the course code, instructor, name, year of a course.

Your School object has two major problems. Let us start with the one you posted in your question:
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
This for loop will always run exactly MAX_PAIRS times, as this variable was defined as
Taken* studentCoursePairs[MAX_PAIRS];
so sizeof(studentCoursePairs) === MAX_PAIRS * sizeof(studentCoursePairs[0]).
Instead, you want to loop only over the first few slots that actually contain valid pointers. You have a variable for that: numTaken. So change the condition to i < numTaken and your print loop will work.
The second major problem is in addTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
Let us play computer and work out what happens if the passed in number and code are valid:
If numTaken is 0, the loop immediately stops (as 0 < 0 is false) and numTaken is not incremented. You can call addTaken as much as you want, it will never change numTaken
Assuming you fix that, let us assume numTaken = 5. On the first iteration, you check the condition and agree this is a valid number-code combination. Thus, you create a new Taken object and .. overwrite studentCoursePairs[0] with the new object. On the second iteration you do the same and overwrite studentCoursePairs[1] with an equivalent object.
That is probably not the intended behavior.
Instead, you probably want to place a new object in studentCoursePairs[numTaken] and bump numTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[numTaken] = taken;
++numTaken;
}
}
Figuring out how to handle the case where the passed combination is NOT valid or when you exceed MAX_PAIRS combinations is left as an exercise to you.
EDIT: There is a third major problem in your CoursesCollection: you allocate space for one object new Course() while you treat it as an array, and you store the result in a local variable instead of a member. Your constructor should probably look like:
CoursesCollection::CoursesCollection(){
courses = new Course*[MAX_COURSES];
numCourses = 0;
}
or, using a member initializer list:
CoursesCollection::CoursesCollection()
: courses(new Course*[MAX_COURSES]), numCourses(0) {}

Related

Unlimited Object Creation in C++

While learning the dynamic object creation in C++ i have encountered a doubt . Here is my code.
And my question is , when the limiting condition in the loop is same as that of the no of objects created it works fine. But what happens when the loop works for more than the size given , it seems printing the values entered , but we have created only 4 objects and changed the condition of loop to more than 4
#include <iostream>
using namespace std;
class item{
int number;
public:
item(){
cout<<"Constructor"<<endl;
}
~item(){
cout<<"Destructor"<<endl;
}
void get_num(int num){
number = num
};
void show_num(){
cout<<"Number is "<<number<<endl;
}
};
const int size=4;
int main() {
item *itemObj = new item[size];
item *d = itemObj; //copy the address of itemObj inorder to access its member functions later
int tempNum;
for (int i = 0; i < size; ++i) {
cout<<"Enter the Number"<<endl;
cin>>tempNum;
itemObj->get_num(tempNum);
itemObj++;
}
//to print the numbers entered
for (int i = 0; i < size; ++i) {
d->show_data();
d++;
cout<<d<<endl;
}
delete itemObj;
return 0;
}
Your code isn't working fine at all. Because you change the value of the pointer that you requested from the new operator. When you call the delete for the itemObj at the last line, it doesn't have its original value.
So, instead of modifying the itemObj, you should modify the copy of it which is the pointer d here. Therefore, the problem isn't about the iteration amount of the loop. It's actually the violation on the heap memory.
Also, if you're creating a dynamic array, you should call delete [] instead of delete.

Vector of a class resetting member variables after for loop

I have an assignment where we need to use this basic structure of vectors and classes to learn about parent and child classes and polymorphism. Here is the code of the function I'm supposed to write:
void assignStudents(vector<Student*>& v) {
for (int i = 0; i < 5; i++)
{
cout << "Enter a study level: ";
string input;
cin >> input;
if (input == "graduate")
{
Graduate inputClass;
Student* inputParentClassPtr = &inputClass;
v.push_back(inputParentClassPtr);
v[i]->addToVector(input);
inputParentClassPtr = nullptr;
}
else if (input == "undergraduate")
{
Undergraduate inputClass;
Student* inputParentClassPtr = &inputClass;
inputParentClassPtr->addToVector(input);
v.push_back(inputParentClassPtr);
}
else
{
cout << "Please enter a valid response, either graduate or undergraduate" << endl;
i--;
}
}
for (size_t i = 0; i < v.size(); i++)
{
vector<string> studyLevels = v[i]->getStudyLevels();
size_t size = studyLevels.size();
for (int j = 0; j < size; j++)
{
cout << studyLevels[j];
}
}
}
I debug the program and every time the first for loop moves on to the next iteration, every member variable inside each object in my vector goes blank, but then when I add a new object into the vector, then call the addToVector() function they come back.
I added the bottom for loop to check if any editing is happening, and once I get to that bottom for loop, every member variable is empty again.
I have the Student class vector where I am adding Undergraduate and Graduate classes to. Every Student class has a protected vector inside called levels. I need to add the class to vector that holds all my objects, then edit the member variable vector to include the string representing the type of class it is.
Why do the member variables (levels) go blank every time it finishes an iteration of the for loop?
I'll just focus on one part, as the same issue appears twice in your code.
{
Graduate inputClass; // create local student "on the stack"
Student* inputParentClassPtr = &inputClass;
v.push_back(inputParentClassPtr); // store address of student
v[i]->addToVector(input);
inputParentClassPtr = nullptr; // has no real effect
} // inputClass goes out of scope and is destroyed here
When the block ends, the local "stack" variables from that block are destroyed. That means the Graduate object is no longer valid, and the pointer you stored in v is now pointing at something unusable.
To fix that, you need to create the objects in dynamic memory.
You should change your vector to store std::unique_ptr<Student>, and create the objects using std::make_unique(), like this:
auto inputParentClassPtr = std::make_unique<Graduate>();
v.push_back(std::move(inputParentClassPtr));
But, if you can't do that, you will need to use new instead, like this:
Student* inputParentClassPtr = new Graduate();
v.push_back(inputParentClassPtr);
Either way, even though inputParentClassPtr is still destroyed at the end of the block, it is only a pointer and the Graduate object it pointed to is still alive.
If you use new, you'll then need to delete all the objects in the vector when you are done using them, or you'll have a memory leak. Using std::unique_ptr will handle that for you.

Can't pass class object array into function?

I am trying to use a function to calculate a grade average and store the letter grade into "letter". However, whenever I try to call the function, I get an error saying "no matching for call to "findAvg". I do not completely understand references and pointers. Is that the issue here? Any help and information is appreciated, thank you.
#include <iostream>
#include <fstream>
using namespace std;
class Student{
public:
double grades[4];
float avgGrade;
string letter;
string name;
};
void findAvg(Student f[]);
int main(){
Student students[3];
int i = 0;
fstream fin;
fin.open("input1.txt");
while(!fin.eof()){
fin >> students[i].name;
for (int j =0; j < 4;++j){
fin >> students[i].grades[j];
}
i += 1;
}
fin. close();
findAvg(students[3]);
cout << students[1].letter;
}
void findAvg(Student f[]){
for(int i = 0;i<3;++i){
f[i].avgGrade = ((f[i].grades[0] + f[i].grades[1] + f[i].grades[2] + f[i].grades[3]) /4);
if (f[i].avgGrade>=90){
f[i].letter = "A";
} else if (89>f[i].avgGrade && f[i].avgGrade<=80){
f[i].letter = "B";
} else if (79>f[i].avgGrade && f[i].avgGrade<=70){
f[i].letter = "C";
} else if (69>f[i].avgGrade && f[i].avgGrade<=60){
f[i].letter = "D";
} else {
f[i].letter = "F";
}
}
}
findAvg(students[3]);
should be
findAvg(students);
The idea that you reference the whole array, by using array[SIZE] (where SIZE is the size of the array) is a common newbie error. I guess it comes from a confusion between the array declaration and an expression. But declarations and expressions are not the same thing and different rules apply. In an expression array[n] always references an element of the array. And furthermore if n is the same as the size of the array then you are referencing an element that does not exist.
The strange thing is that you handle the arrays perfectly correctly in every other part of your code, students[i].grades[j]; for instance. But for some reason when you are calling the findAvg function you think different rules apply.
Try findAvg (students) instead of findAvg (students[3]). students[3] would give you the 4th student, which does not exist.
Your call findAvg(students[3]); is incorrect. You've defined a function that takes an array, but students[3] is a single Student object, not an array. It is also an error because it is trying to access an element outside the boundaries of the array
Try calling it as
findAvg(students);
Here you need to paas the object array
So
use this
findAvg(students);
instead of using
findAvg(students[3]);

Adding element to array of struct c++

Can someone explain why this code does not work? It keeps crashing when it asks for input in addCar().
I think something is wrong with copying an array, but I can't figure out what exactly. I also tried to use copy() but it didn't work either.
#include <iostream>
#include <string>
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
struct Car{
string Brand;
string model;
long mileage;
};
void addCar(int *ptr, struct Car *arra){
*ptr=*ptr+1;
Car *newArr = new Car[*ptr];
memcpy(newArr, arra, (*ptr)*sizeof(Car));
cout<<"Brand ";
getline(cin,newArr[*ptr].Brand);
cout<<"Model ";
getline(cin, newArr[*ptr].model);
cout<<"mileage ";
cin>>newArr[*ptr].mileage;
arra=newArr;
};
int main(int argc, char** argv) {
int size=1;
int *ptr_size;
ptr_size=&size;
Car *tab=new Car[*ptr_size];
tab[0].Brand = "Audi";
tab[0].model = "A8";
tab[0].mileage = 14366;
addCar(*ptr_size, tab);
return 0;
}
The fail is probably here:
getline(cin,newArr[*ptr].Brand);
A bit above, you did this: *ptr=*ptr+1; and made newArr an array of *ptr elements. Arrays are origin zero. That means the first item in the array is newArr[0]. The last will be at newArr[*ptr-1], so writing into newArr[*ptr] is writing over someone else's memory. Generally a bad thing to do.
But this is also not cool:
*ptr=*ptr+1;
Car *newArr = new Car[size+1];
memcpy(newArr, arra, (*ptr)*sizeof(Car));
You increment the size of the array. That's OK.
You create a new array with the new size. That's OK.
You copy new size number of elements from the old array to the new array and over shoot the end of the old array. Not OK.
The best answer is given by Jerry Coffin and Paul McKenzie in the comments: use a std::vector. If this is not allowed... Ick.
But alrighty then.
First, memcpy literally copies a block of memory. It does not know or care what that block of memory is or what it contains. Never use memcpy unless you are copying something really, really simple like basic data type or a structure made up of nothing but basic data types. String is not basic. The data represented by a string might not be inside the string. In that case, you copy a pointer to the string and that pointer will not be valid after the death of the string. That's not a problem in your case because you don't kill the string. That leads to problem 2. Let's fix that before you get there. The easiest way (other than vector) is going to be:
for (int index = 0; index < *ptr-1; index++)
{
newArr[index] = arra[index];
}
An optimization note. You don't want to resize and copy the array every time you add to it. Consider having two integers, one size of array and the other index into array and double the size of the array every time the index is about to catch up with the size.
When you allocate any memory for data with new somebody has to clean up and put that memory back with delete. In C++ that somebody is you. so, before you arra=newArr; you need to delete[] arra;
Passing in the array index as a pointer overcomplicates. Use a reference or just pass by value and return the new index. Also, don't name a variable ptr. Use something descriptive.
void addCar(int &arrasize, struct Car *arra){
or
int addCar(int arrasize, struct Car *arra){
Next problem: int addCar(int arrasize, struct Car *arra){ passes in a pointer to arra. But you passed the pointer by value, made a copy of the pointer, so when you change the pointer inside the function, it's only the copy that got changed and the new array is not going to come back out again. So,
int addCar(int arrasize, struct Car * & arra){
Passes in a reference to the pointer and allows you to modify the pointer inside the function.
Putting all that together:
int addCar(int size, struct Car * & arra)
{
Car *newArr = new Car[size + 1];
for (int index = 0; index < size; index++)
{
newArr[index] = arra[index];
}
cout << "Brand ";
getline(cin, newArr[size].Brand);
cout << "Model ";
getline(cin, newArr[size].model);
cout << "mileage ";
cin >> newArr[size].mileage;
delete[] arra;
arra = newArr;
return size+1;
}
int main()
{
int size=1;
Car *tab=new Car[size];
tab[0].Brand = "Audi";
tab[0].model = "A8";
tab[0].mileage = 14366;
size = addCar(size, tab);
// do more stuff;
// bit of test code here
for (int index = 0; index < size; index++)
{
cout << "Car " << index << " brand =" <<tab[index].Brand << " Model=" << tab[index].model << " mileage=" <<tab[index].mileage << endl;
}
delete[] tab;
return 0;
}
When you are copying the old array to the new one you are accessing invalid memory, remember that, in that point, arra has size *ptr-1 not *ptr, so the line should be
memcpy(newArr, arra, (*ptr-1)*sizeof(Car));
also in the other lines you should insert the new value in the *ptr-1 position because the indexes in newArr go from 0 to size-1 ie *ptr-1:
cout<<"Brand ";
getline(cin,newArr[*ptr-1].Brand);
cout<<"Model ";
getline(cin, newArr[*ptr-1].model);
cout<<"mileage ";
cin>>newArr[*ptr-1].mileage;

C++ Multidimensional arrays generating segmentation faults?

I am writing a script which must copy some names into a multidimensional array, print the contents of the array and then deallocate the memory and terminate. The problem I am having is that when I run the script it only prints out the last name entered. Here is what I have done. Any help would be great! Thanks in advance!
#include <iostream>
#include <string.h>
using namespace std;
void createArray(int n);
void addDetail(char*& name, char*& surname);
void printArray();
void clear();
char ***details;
int used;
int size;
int main()
{
createArray(3);
char* tmpName = new char[20];
char* tmpSurName = new char[120];
strcpy(tmpName, "nameA");
strcpy(tmpSurName, "surnameA");
addDetail(tmpName,tmpSurName);
strcpy(tmpName, "nameB");
strcpy(tmpSurName, "surnameB");
addDetail(tmpName,tmpSurName);
strcpy(tmpName, "nameC");
strcpy(tmpSurName, "surnameC");
addDetail(tmpName,tmpSurName);
clear();
return 0;
}
void createArray(int n)
{
details= new char**[n];
for(int i=0; i<n; i++)
details[i] = new char*[2];
size = n;
used = 0;
}
void addDetail(char*& name, char*& surname)
{
if(occupation < size)
{
details[used][0] = name;
details[used][1] = surname;
used++;
}else{
cout << "Array Full " << endl;
}
}
void printArray()
{
for(int i=0; i<used; i++)
cout << details[i][0] << " " << details[i][1] << endl;
}
void clear()
{
for(int i=0; i<size; i++)
{
delete [] details[i];
details[i] = 0;
}
delete [] details;
details=0;
}
You didn't allocate memory for details[used][0] and details[used][1] so it's using whatever random address was in those locations.
Since this is C++ you can use string instead perhaps? std::string **details;. This should work with your existing code, except that it will leak memory.
Better still is to use a vector of vectors.
Something like:
std::vector<std::vector<std::string> > details;
Then the createArray function can go away completely and addDetail becomes simpler:
std::vector<string> newName;
newName.push_back(name);
newName.push_back(surname);
details.push_back(newName);
It is because each time, you are effectively storing the pointer tmpName and tmpSurName in the array details. Then in the next iteration, you overwrite the contents of the memory where tmpName and tmpSurName point to, so at the end you'll have a list that contains the last name/surname n times.
To solve it, you need to re-allocate tmpName and tmpSurName before each call to addDetail.
Btw, why do you need to use an (ewww) char***, and can't use e.g. the STL?
What it looks like is happening is that you are not adding the string to the array, you are adding a pointer to name and surname. Every instance is pointing at that variable, so when you ask the array what it contains it goes and asks name and surname, which will only contain the last value.
Also that array, are you sure its working how you want it to work? Arrays are... concrete things. Your essentially saying "I want 5 of these, they will be this big (based on the type you put in)" and the computer quietly goes "well I'll set aside space for those here and you can put them in when your ready". When your code puts those names in there, there really isn't any prep on where to store them. If you fill up that space and go beyond you go to bad places. So what you should do is have that last * of char*** be a pointer to a char[120] so that you know (for your purposes atleast) it never gets filled up. Do that in your createArray function after you have created the outer arrays.
You keep overwriting your temporary buffers rather than making new buffers for each entry in the array. As a result, only the last data written to the buffer survives.
Here's a rough guide on one way to fix it, though this sample may have some problems - I made no attempt to compile or test this.
This portion of main belongs in addDetail:
char* tmpName = new char[20];
char* tmpSurName = new char[120];
strcpy(tmpName, "nameA");
strcpy(tmpSurName, "surnameA");
So, your new addDetail would look something like:
void addDetail(char*& name, char*& surname)
{
if(occupation < size)
{
details[used][0] = new char[20];
details[used][1] = new char[120];
strcpy(details[used][0], name);
strcpy(details[used][1], surname);
used++;
}else{
cout << "Array Full " << endl;
}
}
And it would be called from main like:
addDetail("nameA", "surnameA");
You'd need to update clear to properly cleanup the allocations made in addDetail though.