C++ ошибка сегментации
Уже несколько дней пытаюсь найти ошибку, все никак не получается :(. При этом смотрю на пример, который мы делали с другом, делаю в этой работе все также, но что-то не так.
Привожу фрагмент кода:
class stack{ // класс "стек"
public:
class cars{ //класс машины (марки машин)
public:
string mark;
string color;
string adress;
int price;
cars(){} // конструктор умолчания
cars(string m, string c, int p, string a){ // конструктор инициализации
mark = m;
color = c;
adress = a;
price = p;
}
cars operator=(cars cc){ // оператор присваивания машин
mark = cc.mark;
color = cc.color;
adress = cc.adress;
price = cc.price;
return *this;
}
void print(){ // вывод элементов на уровне класса "машины"
cout << endl << mark << ", цвет: " << color << ", цена: " << price << " адрес: " << adress << endl;
}
~cars(){} // деструктор
};
stack (){ // конструктор умолчания
car = NULL;
size = 0;
}
int size = 0; // количество машин в стекек (сначала 0)
cars *car; // машины в стеке
void push(){ // добавление элементов
int price;
string mark, color, adress, pric;
cout << "\nВведите марку машины: ";
cin >> mark;
cout << "\nВведите цвет машины: ";
cin >> color;
cout << "\nВведите цену: ";
cin >> pric;
price = iscorrect(pric);
cout << "\nВведите адрес: ";
cin.ignore();
getline(cin, adress);
cars ccc(mark, color, price, adress);
ccc.print();
size++;
if (size == 1) car = (cars*)malloc(1 * sizeof(cars)); // если в стэке пока нет машин, назначить память для первой
else car = (cars*)realloc(car, size * sizeof(cars)); //иначе назначит на текущее количество + следующий элемент
car[size - 1]=ccc;
cout << "\nЭта машина успешно добавлена в список\n";
}
void pop() { // удаление элементов
car[size - 1].print();
size--;
car = (cars*)realloc(car, (size) * sizeof(cars)); //переназначение памяти
cout << "\nЭта машина успешно удалена.\n";
}
class stack{ // класс "стек"
public:
class cars{ //класс машины (марки машин)
public:
string mark;
string color;
string adress;
int price;
cars(){} // конструктор умолчания
cars(string m, string c, int p, string a){ // конструктор инициализации
mark = m;
color = c;
adress = a;
price = p;
}
cars operator=(cars cc){ // оператор присваивания машин
mark = cc.mark;
color = cc.color;
adress = cc.adress;
price = cc.price;
return *this;
}
void print(){ // вывод элементов на уровне класса "машины"
cout << endl << mark << ", цвет: " << color << ", цена: " << price << " адрес: " << adress << endl;
}
~cars(){} // деструктор
};
stack (){ // конструктор умолчания
car = NULL;
size = 0;
}
int size = 0; // количество машин в стекек (сначала 0)
cars *car; // машины в стеке
void push(){ // добавление элементов
int price;
string mark, color, adress, pric;
cout << "\nВведите марку машины: ";
cin >> mark;
cout << "\nВведите цвет машины: ";
cin >> color;
cout << "\nВведите цену: ";
cin >> pric;
price = iscorrect(pric);
cout << "\nВведите адрес: ";
cin.ignore();
getline(cin, adress);
cars ccc(mark, color, price, adress);
ccc.print();
size++;
if (size == 1) car = (cars*)malloc(1 * sizeof(cars)); // если в стэке пока нет машин, назначить память для первой
else car = (cars*)realloc(car, size * sizeof(cars)); //иначе назначит на текущее количество + следующий элемент
car[size - 1]=ccc;
cout << "\nЭта машина успешно добавлена в список\n";
}
void pop() { // удаление элементов
car[size - 1].print();
size--;
car = (cars*)realloc(car, (size) * sizeof(cars)); //переназначение памяти
cout << "\nЭта машина успешно удалена.\n";
}
clang version 7.0.0-3~ubuntu0.18.04.1 (tags/RELEASE_700/final) Весь можно запустить здесь: https://repl.it/repls/DarksalmonCrookedPaint#main.cpp
В чем сама проблема: почему-то только если добавить один элемент, потом сразу удалить его, после попытки добавления еще одного элемента выходит ошибка сегментации.
Но дело в том, что если изначально добавить два элемента и больше, никакая ошибка не появляется: хоть добавь два, потом удали их, опять же как выше добавь один и сразу удали его. Все будет хорошо. Привожу скриншоты:
Вот первый случай:

В среде Visual Studio 19 ошибка такая:

Кажется, моих неглубоких знаний не хватит, чтобы понять суть этой проблемы, поэтому я очень рассчитываю на помощь. Спасибо.
Ответы (2 шт):
Раз вы хотите кода... Я бы делал примерно так:
class Car{ //класс машины (марки машин)
public:
Car(){} // конструктор умолчания
Car(string m, string c, int p, string a):mark(m),color(c),adress(a),price(p){}
Car(const Car&) = default;
Car& operator=(const Car&) = default;
~Car() = default;
void print(){ // вывод элементов на уровне класса "машины"
cout << endl << mark << ", цвет: " << color << ", цена: " << price << " адрес: " << adress << endl;
}
private:
string mark;
string color;
string adress;
int price;
};
Car read()
{
int price;
string mark, color, adress;
cout << "\nВведите марку машины: ";
cin >> mark;
cout << "\nВведите цвет машины: ";
cin >> color;
cout << "\nВведите адрес: ";
cin >> adress;
cout << "\nВведите цену: ";
cin >> price;
return Car(mark, color, price, adress);
}
class stack { // класс "стек"
public:
stack(): cars(new Car[4]), size(0), capacity(4) {}
stack(const stack&) = delete;
stack& operator=(const stack&) = delete;
~stack() { delete[] cars; }
void push(const Car& c)
{
if (size == capacity)
{
Car * tmp = new Car[capacity*=2];
for(int i = 0; i < size; ++i) tmp[i] = cars[i];
delete[] cars;
cars = tmp;
}
cars[size++] = c;
}
Car pop()
{
if (size == 0) throw runtime_error("Stack is empty");
return cars[--size];
}
private:
int size, capacity;
Car *cars;
};
int main(int argc, const char * argv[])
{
stack st;
st.push(read());
st.pop().print();
st.push(read());
st.push(read());
st.pop().print();
st.pop().print();
}
Для того чтобы объяснить ситуацию (на которую вам вообще-то указывает компилятор, о чем я писал в комментах) приведу небольшой пример:
// main.cpp
#include <iostream>
#include <string>
using namespace std;
class Tmp {
public:
Tmp() {
str = "string";
std::cout << "constructor" << std::endl;
}
Tmp &operator=(const Tmp &rhs) {
str = rhs.str;
std::cout << "copy constructor" << std::endl;
return *this;
}
~Tmp() {
std::cout << "destructor" << std::endl;
}
std::string str;
};
int main() {
Tmp *val = (Tmp *)malloc(1 * sizeof(Tmp));
Tmp stackVal;
*val = stackVal;
free(val);
}
Как видим у нас есть базовый класс который содержит в себе строку а конструктор по умолчанию, копирующий конструктор и деструктор я реализовал с выводом, чтобы было видно когда они вызываются. Запустим:
constructor
copy constructor
destructor
Казалось бы ничего не обычного, но если присмотреться: у нас было вызвано два консруктора - и всего один деструктор... Для наглядности как это работает с new и delete:
int main() {
Tmp *val = new Tmp;
Tmp stackVal;
*val = stackVal;
delete (val);
}
Вывод:
constructor
constructor
copy constructor
destructor
destructor
Вот это уже другой дело. Но почему так? Да потому что malloc, free и realloc сишный функции, они ничего не знают о конструкторах и деструкторах! Если бы вы скомпилировали ваш код с включенными варнингами то обнаружили бы что-то типа того:
main.cpp:27:49: warning: ‘void* realloc(void*, size_t)’ moving an object of non-trivially copyable type ‘class Tmp’; use ‘new’ and ‘delete’ instead [-Wclass-memaccess]
Но почему же тогда код с malloc и т.п. работает у вашего друга? - спросите вы. А не работает он и там, только там еще хуже, потому что программа не падает сразу. Посмотрим на следующий пример:
int main() {
Tmp *val = (Tmp *)malloc(1 * sizeof(Tmp));
std::memset((char *)val, 1, sizeof(Tmp));
Tmp stackVal;
*val = stackVal;
free(val);
}
И мы получаем segmentation fault. Но вы же сами установили невалидные значения в память выделенную под val - конечно оно падает! - скажете вы. На что я отвече: да, я установил невалидные значения в память, выделенную под эту переменную, НО, а откуда там могут взятся валидные значения? malloc - повторюсь, ничего не знает о конструкторах! И доже память, которую он выделяет, не обязательно забита нулями - там находится мусор! Просто иногда так получается, что там находятся одни нули и все "как бы работает" - но нет, оно не работает!
В заключение: используйте new и delete
