Помогите разобраться

jazz323
Offline
Зарегистрирован: 23.10.2016

Программа работала, но на каждой итерации жрала память. Я переделал чтобы этого не было, но возникла проблема. Помогите разобраться почему после этой функции у меня меняется вектор математической модели, который вроде бы в ней никак не участвует (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;
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

jazz323 пишет:
Программа работала, но на каждой итерации жрала память.

А что, сейчас не жрёт? Жрёт за милую душу. Только при этом она ещё и всё в кучу сваливает.

jazz323 пишет:
Я переделал ...

Ну-ну ...

jazz323 пишет:
подскажите как исправить.

Переписать с нуля.

Не обижайтесь, но то, что здесь написано является полным бредом. Вот посмотрите пример (и это только одна маленькая проблемка).

Начало моего примера - это кусок Вашего кода с объявлением классов без каких либо изменений, кроме того, что я вставил две печати, который обозначил комментарием /**********************/, чтобы их было видно.

В конце (функция 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() {}

И что же она печатает?

Любуемся:

ZAPROS pamyati
DO printVector
1.00
2.00
3.00

OSVOBOZHDENIE pamyati
POSLE printVector
OSVOBOZHDENIE pamyati

Сначала всё нормально - запросила память, потом напечатала вектор (причём, правильно напечатала).

А вот дальше (строка 7) она вдруг освобождает память (ещё до выхода из printVector!!!!) Опаньки, а кто ж её просил? Значит, после выхода из printVector память вектора уже освобождена и на её место кто хочет может писать что хочет!

Более того, при выходе из setup она ещё раз освобождает уже освобождённую память (последняя строка)!

Вы видите, что тут мешанина и бред? И с памятью может твориться всё, что угодно? Коль скоро она освобождена, то в неё можно писать что угодно, а при повторном освобождении ... 

Виноват не printVector, виновата Ваша организация работы с памятью, поэтому точно такие же фокусы будут и с другими функциями. printVector только один из возможных примеров. Проблема глобальна по всей программе.

Так что не обижайтесь, но поправить в данном случае  == переписать полностью.

Так с памятью в классах не работают.

 

jazz323
Offline
Зарегистрирован: 23.10.2016

Спасибо за разъяснение. Не могли бы вы посоветовать что надо прочитать для правильной работы с памятью в классах? 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ключевые слова: "коструктор копирования" и "перегрузка оператора присваивания". Поищите.

Дело в том, что Вы передаёте экземпляр класса по значению. При это создаётся новая копия экземпляра и старая целиком копируется в новую. Но ведь у Вас в классе указатель! Так вот именно он и копируется! Т.е. не выделяется новая память, не копируется, а просто копируется указатель. В результате указатель вновь созданной копии экземпляра указывает не на собственный массив, а на массив оригинального экземпляра. понимаете. Потом, когда копия уничтожается (перед выходом из функции) вызывается деструктор, который освобождает паямть. Он и освобождает память оригинального экземпляра, т.к. собственную память для копии никто вообще-то и не запрашивал.

Этим объясняется странное поведение в примере, который я приводитл.

точно такая же беда возникает не только при передаче по значению, а ещё и при присваивании. Вот объявили Вы 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) {}

он печатает

Problem #1:
Constructor PoorClass: m_ptr=642
Constructor PoorClass: m_ptr=654
Point #1: a.m_ptr=642;   b.m_ptr=654
Point #2: a.m_ptr=654;   b.m_ptr=654
Destructor PoorClass: m_ptr=654
Destructor PoorClass: m_ptr=654

Problem #2:
Constructor PoorClass: m_ptr=666
Point #3: &a=2298;   a.m_ptr=666
Point #4: &p=2296;   p.m_ptr=666
Destructor PoorClass: m_ptr=666
Destructor PoorClass: m_ptr=666

давайте разбираться

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) {}

Выдаёт

Problem #1:
Constructor PoorClass: m_ptr=642
Constructor PoorClass: m_ptr=654
Point #1: a.m_ptr=642;   b.m_ptr=654
Point #2: a.m_ptr=642;   b.m_ptr=654
Destructor PoorClass: m_ptr=654
Destructor PoorClass: m_ptr=642

Problem #2:
Constructor PoorClass: m_ptr=666
Point #3: &a=2298;   a.m_ptr=666
Point #4: &p=2296;   p.m_ptr=654
Destructor PoorClass: m_ptr=654
Destructor PoorClass: m_ptr=666

Как видите ни проблемы присваивания, ни проблемы копирования больше нет.

jazz323
Offline
Зарегистрирован: 23.10.2016

Спасибо большое, за ваше потраченное время. Вы объяснили все довольно понятно и лакончично. Без вашей помощи я бы скорее всего и не разобрался.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Память по-любому надо починить, но Вы бы ещё подумали, а надо ли Вам так часто пользоваться передачей параметров по значению. Ну, вот функция printVector - ей нужно вектор напечатать, и Вы передаёте по значению, т.е. ради простой печати Вы создаёте новый экземпляр, должны запросить новую память, скопировать её. И всё это ради простой печати. Оно Вам надо? Передавайте по ссылке или указатель, и никаких новых экземпляров, никаких запросов памяти - тишь, да гладь.