Помогите разобраться
- Войдите на сайт для отправки комментариев
Вс, 27/11/2016 - 19:12
Программа работала, но на каждой итерации жрала память. Я переделал чтобы этого не было, но возникла проблема. Помогите разобраться почему после этой функции у меня меняется вектор математической модели, который вроде бы в ней никак не участвует (pModel->Vec), и вектор (VecR) из которой только берут его размер и подскажите как исправить.
Vector RParts (Vector Vec0, double t){
Serial.println("\tIn function RParts");
Serial.print("\t\t");
printVector(Vec0);
Vector VecK(Vec0.n);
VecK.VS[0] = sin(t);//+
return VecK;
}
Вот полный код:
struct Vector
{
public:
Vector(){
};
int n;//=1;
float *VS;
void Init(int an){
n=an;
VS=new float [n];
}
Vector(int an){
Init(an);
}
~Vector(){
delete[] VS;
}
};
void printVector(Vector Vec){
for (int i=0; i<Vec.n; i++)
Serial.println(Vec.VS[i]);
Serial.println();
};
class AMatModel {
public:
Vector Vec;
AMatModel(int an){
Vec.Init(an);
};
// AMatModel(){
// delete[] Vec.VS;
// };
//virtual Vector RParts(Vector Vec0, double t)=0;
};
class MatModel : public AMatModel{
public :
MatModel(int an):AMatModel(an){
};
Vector RParts (Vector Vec0, double t){
Serial.println("\tIn function RParts");
Serial.print("\t\t");
printVector(Vec0);
Vector VecK(Vec0.n);
VecK.VS[0] = sin(t);//+
return VecK;
}
};
class AInt {
public:
MatModel *pModel;
double h;
Vector VecR, VecI;
virtual Vector Run(MatModel *pModel, double t, double tk)=0;
};
class RInt : public AInt {
public:
Vector Run(MatModel *pModel, double t, double tk)
{
Serial.println("In function Run");
Serial.print("\t");
printVector(pModel->Vec);
VecI.Init(pModel->Vec.n);
VecR.Init(pModel->Vec.n);
for (int i=0; i< VecR.n;i++)
VecR.VS[i] = pModel->Vec.VS[i];
Serial.print("\t");
printVector(VecR);
do {
VecI = pModel->RParts(VecR, t);
Serial.print("\t");
printVector(VecR);
Serial.print("\t");
printVector(pModel->Vec);
for (int i = 0; i<VecR.n; i++) {
VecR.VS[i] += h * VecI.VS[i];
Serial.print("\t");
printVector(VecR);
}
t += h;
} while (t<=tk-h);
return VecR;
};
};
MatModel MM(1);
RInt Integr;
Vector Vec;
double t0, tk, dt;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
for (int i=0; i<MM.Vec.n;i++)
MM.Vec.VS[i]=-1;
t0=0;
tk=1;
dt=0.1;
}
void loop() {
// put your main code here, to run repeatedly:
Vec.Init(1);
// int l=0;
// if (l==0){
// for (int i=0; i<MM.Vec.n;i++)
// MM.Vec.VS[i]=-1;
// l=1;
// }
// Serial.print("l=");
// Serial.println(l);
Integr.h=0.02;
//Vec.VS[0]=0;
Serial.println("Vector with coordinates:");
printVector(MM.Vec);
Serial.println("Vector with coordinates:");
printVector(MM.Vec);
Vec=Integr.Run(&MM,t0, t0+dt );
Serial.println("Vector with coordinates after integr:");
printVector(MM.Vec);
for (int i=0; i<Vec.n;i++)
MM.Vec.VS[i]=Vec.VS[i];
// Serial.print("Time:\t");
//Serial.println(millis());
delay(500);
t0=t0+dt;
Serial.print("t0=");
Serial.println(t0);
printVector(MM.Vec);
Serial.print("-Cosinus= ");
Serial.println(-cos(t0));
Serial.print("Free memory:");
Serial.println(memoryFree());
delete[] Vec.VS;
Serial.print("Free memory:");
Serial.println(memoryFree());
}
extern int __bss_end;
extern void *__brkval;
// Функция, возвращающая количество свободного ОЗУ (RAM)
int memoryFree()
{
int freeValue;
if((int)__brkval == 0)
freeValue = ((int)&freeValue) - ((int)&__bss_end);
else
freeValue = ((int)&freeValue) - ((int)__brkval);
return freeValue;
}
А что, сейчас не жрёт? Жрёт за милую душу. Только при этом она ещё и всё в кучу сваливает.
Ну-ну ...
Переписать с нуля.
Не обижайтесь, но то, что здесь написано является полным бредом. Вот посмотрите пример (и это только одна маленькая проблемка).
Начало моего примера - это кусок Вашего кода с объявлением классов без каких либо изменений, кроме того, что я вставил две печати, который обозначил комментарием /**********************/, чтобы их было видно.
В конце (функция setup) я написал простенькую программку, посмотрите сами.
struct Vector { public: Vector(){ }; int n;//=1; float *VS; void Init(int an){ n=an; VS=new float [n]; Serial.println("ZAPROS pamyati"); /**********************/ } Vector(int an){ Init(an); } ~Vector(){ Serial.println("OSVOBOZHDENIE pamyati"); /**********************/ delete[] VS; } }; void printVector(Vector Vec){ for (int i=0; i<Vec.n; i++) Serial.println(Vec.VS[i]); Serial.println(); }; class AMatModel { public: Vector Vec; AMatModel(int an){ Vec.Init(an); }; // AMatModel(){ // delete[] Vec.VS; // }; //virtual Vector RParts(Vector Vec0, double t)=0; }; class MatModel : public AMatModel{ public : MatModel(int an):AMatModel(an){ }; Vector RParts (Vector Vec0, double t){ Serial.println("\tIn function RParts"); Serial.print("\t\t"); printVector(Vec0); Vector VecK(Vec0.n); VecK.VS[0] = sin(t);//+ return VecK; } }; class AInt { public: MatModel *pModel; double h; Vector VecR, VecI; virtual Vector Run(MatModel *pModel, double t, double tk)=0; }; class RInt : public AInt { public: Vector Run(MatModel *pModel, double t, double tk) { Serial.println("In function Run"); Serial.print("\t"); printVector(pModel->Vec); VecI.Init(pModel->Vec.n); VecR.Init(pModel->Vec.n); for (int i=0; i< VecR.n;i++) VecR.VS[i] = pModel->Vec.VS[i]; Serial.print("\t"); printVector(VecR); do { VecI = pModel->RParts(VecR, t); Serial.print("\t"); printVector(VecR); Serial.print("\t"); printVector(pModel->Vec); for (int i = 0; i<VecR.n; i++) { VecR.VS[i] += h * VecI.VS[i]; Serial.print("\t"); printVector(VecR); } t += h; } while (t<=tk-h); return VecR; }; }; /***************************************************************/ void setup() { Serial.begin(115200); Vector v(3); v.VS[0] = 1; v.VS[1] = 2; v.VS[2] = 3; Serial.println("DO printVector"); printVector(v); Serial.println("POSLE printVector"); } void loop() {}И что же она печатает?
Любуемся:
Сначала всё нормально - запросила память, потом напечатала вектор (причём, правильно напечатала).
А вот дальше (строка 7) она вдруг освобождает память (ещё до выхода из printVector!!!!) Опаньки, а кто ж её просил? Значит, после выхода из printVector память вектора уже освобождена и на её место кто хочет может писать что хочет!
Более того, при выходе из setup она ещё раз освобождает уже освобождённую память (последняя строка)!
Вы видите, что тут мешанина и бред? И с памятью может твориться всё, что угодно? Коль скоро она освобождена, то в неё можно писать что угодно, а при повторном освобождении ...
Виноват не printVector, виновата Ваша организация работы с памятью, поэтому точно такие же фокусы будут и с другими функциями. printVector только один из возможных примеров. Проблема глобальна по всей программе.
Так что не обижайтесь, но поправить в данном случае == переписать полностью.
Так с памятью в классах не работают.
Спасибо за разъяснение. Не могли бы вы посоветовать что надо прочитать для правильной работы с памятью в классах?
Ключевые слова: "коструктор копирования" и "перегрузка оператора присваивания". Поищите.
Дело в том, что Вы передаёте экземпляр класса по значению. При это создаётся новая копия экземпляра и старая целиком копируется в новую. Но ведь у Вас в классе указатель! Так вот именно он и копируется! Т.е. не выделяется новая память, не копируется, а просто копируется указатель. В результате указатель вновь созданной копии экземпляра указывает не на собственный массив, а на массив оригинального экземпляра. понимаете. Потом, когда копия уничтожается (перед выходом из функции) вызывается деструктор, который освобождает паямть. Он и освобождает память оригинального экземпляра, т.к. собственную память для копии никто вообще-то и не запрашивал.
Этим объясняется странное поведение в примере, который я приводитл.
точно такая же беда возникает не только при передаче по значению, а ещё и при присваивании. Вот объявили Вы Vector a, b; Поработали с ними, а потом решили присвоить один другому (a = b). Возникнет точно такая же проблема - при присваивании b будет полностью скопирован в a. Т.е. указатель в a станет рвным указателю в b. Они будут указывать на одну и ту же память (запрошенную для b), а про память, запрошенную для a просто забудут.
давайте я покажу это на простейшме примере, а то у Вас класс довольно сложный и там менее понятно.
Вот, смотрите такой пример:
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; } struct PoorClass { PoorClass(void) { m_ptr = new char [10]; Serial << "Constructor PoorClass: m_ptr=" << ((int)m_ptr) << "\n"; } ~PoorClass(void) { Serial << "Destructor PoorClass: m_ptr=" << ((int)m_ptr) << "\n"; m_ptr = new char [10]; } char *m_ptr; }; void someFunction(PoorClass p) { Serial << "Point #4: &p=" << ((int)&p) << "; p.m_ptr=" << ((int)p.m_ptr) << '\n'; } void setup(void) { Serial.begin(115200); // Проблема №1. Присваивание { Serial << "Problem #1:\n"; PoorClass a, b; Serial << "Point #1: a.m_ptr=" << ((int)a.m_ptr) << "; b.m_ptr=" << ((int)b.m_ptr) << '\n'; a = b; Serial << "Point #2: a.m_ptr=" << ((int)a.m_ptr) << "; b.m_ptr=" << ((int)b.m_ptr) << '\n'; } // Проблема №2. Передача по значению { Serial << "\nProblem #2:\n"; PoorClass a; Serial << "Point #3: &a=" << ((int)&a) << "; a.m_ptr=" << ((int)a.m_ptr) << '\n'; someFunction(a); } } void loop(void) {}он печатает
давайте разбираться
1. проблема присваивания
В строке 26 мы создали два экземпляра. Конструктор любезно сообщил нам адреса запрошенных кусков памяти. Потом мы напечатали эти экземпляры (строка 27 программы и строка 4 печати). Видим. что всё в порядке, адреса правильные. Так?
Теперь мы присваиваем в a = b; строке 28 и снова печатаем (строки 29 и 5) и что видим в строке 5 выдачи? Массивы имеют одинаковый адрес 654! А когда пришла пора экземплярам удаляться, оба деструктора освободили именно этот адрес. Про оригинальный адрес массива из a (642) просто забыли!
Понятна проблема?
2. проблема передачи по значению
Здесь я печатаю адрес самого экземпляра и адрес запрошенного для него массива. Не буду так подробно, но заметьте, то, что печатается изнутри функции показывает другой адрес экземпляра (т.е. локальный экземпляр для функции честно создан!) но у этого локального экезмпляра адрес массива точно такой же, какой был и о оригинала. При выходе из функции локальный экземпляр уничтожается и деструктор освобождает память его массива. Т.е. оригинальный (объявленный до функции) экземпляр остался без массива - его освободили.
Разобрались? Понимаете суть проблемы?
Эти проблемы имеют общий корнеь - а именно тупое копирование оригинального экземпляра в новый при присваивании и передаче в функцию.
Для того, чтобы проблему решить, нужно самому задать процедуру присваивания и создания экземпляра. И там написать как нам надо. Именно это делается оператором присваивания и конструктором копирования.
Посмотрите тот же самый пример, в котором обе проблемы решены. Код вроде прокомментирован, разберётесь
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; } struct PoorClass { PoorClass(void) { m_ptr = new char [10]; Serial << "Constructor PoorClass: m_ptr=" << ((int)m_ptr) << "\n"; } ~PoorClass(void) { Serial << "Destructor PoorClass: m_ptr=" << ((int)m_ptr) << "\n"; m_ptr = new char [10]; } // // Это самодельный оператор присваивания для нашего класса // Здесь мы явно говорим, что при присваивании не нужно копировать // указатель, а нужно копировать массив // // Осторожно - упрощение!!!! // Здесь мы явно эксплуатируем тот факт, что массив у нас всегда 10 байтов // Если бы массив имел переменную длину, было бы чуть сложнее. Нам надо было бы // 1) освободить "свою" памть // 2) запрсить новую // 3) скопировать из оригинала в новую // (разумеется, можно было бы оптимизировать, типа "если размер // одинаковый, то не освобождать и т.п.") PoorClass & operator = (const PoorClass &original) { memcpy(m_ptr, original.m_ptr, 10); return *this; } // // Это конструктор копирования // он решает проблему создания экземплряра при передаче в функцию // Мы не пишем его, а тупо используем уже имеющийся у нас код // оператора присваивания (что не всегда корректно, но здесь - нормально) PoorClass(const PoorClass &original) { *this = original; } char *m_ptr; }; void someFunction(PoorClass p) { Serial << "Point #4: &p=" << ((int)&p) << "; p.m_ptr=" << ((int)p.m_ptr) << '\n'; } void setup(void) { Serial.begin(115200); // Проблема №1. Присваивание { Serial << "Problem #1:\n"; PoorClass a, b; Serial << "Point #1: a.m_ptr=" << ((int)a.m_ptr) << "; b.m_ptr=" << ((int)b.m_ptr) << '\n'; a = b; Serial << "Point #2: a.m_ptr=" << ((int)a.m_ptr) << "; b.m_ptr=" << ((int)b.m_ptr) << '\n'; } // Проблема №2. Передача по значению { Serial << "\nProblem #2:\n"; PoorClass a; Serial << "Point #3: &a=" << ((int)&a) << "; a.m_ptr=" << ((int)a.m_ptr) << '\n'; someFunction(a); } } void loop(void) {}Выдаёт
Как видите ни проблемы присваивания, ни проблемы копирования больше нет.
Спасибо большое, за ваше потраченное время. Вы объяснили все довольно понятно и лакончично. Без вашей помощи я бы скорее всего и не разобрался.
Память по-любому надо починить, но Вы бы ещё подумали, а надо ли Вам так часто пользоваться передачей параметров по значению. Ну, вот функция printVector - ей нужно вектор напечатать, и Вы передаёте по значению, т.е. ради простой печати Вы создаёте новый экземпляр, должны запросить новую память, скопировать её. И всё это ради простой печати. Оно Вам надо? Передавайте по ссылке или указатель, и никаких новых экземпляров, никаких запросов памяти - тишь, да гладь.