Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Освобождение памяти в структуре

#cpp


Когда читал про выделение памяти с помощью new  и ее освобождения, возник вопрос.
Вот пример кода:

const int size_m = 40;    

int main ()
{   
  struct car
  {
     char *name;
  };

  int QCar;
  cout << "How many car?";
  cin >> QCar;
  car *pCar = new car[QCar];
  for ( int i = 0; i < QCar; i++)
  {
     cout << "Car name: ";
     char temp [size_m];
     char >> temp;
     char *ptemp = new char [strlen(temp)+1];
     strcpy(ptemp,temp);
     pCar[i].name = ptemp;
  }
  cout << " Your auto"<< endl;
  for ( int i = 0; i < QCar; i++)
  {
     cout << pCar[i].name;
  }
  delete []pCar;
  system("pause");
  return 0;
 }


Является ли достаточным только это освобождение или нужно еще. Или вообще иначе.
Просто выделение памяти дважды , а удаление одно.
    


Ответы

Ответ 1



Почти правильно :) Сейчас поясню, почему "почти". В C++ у каждого объекта есть такая вешь, как конструктор и деструктор. Если их не написать самому - их сгенерирует компилятор (все несколько сложнее, но я упрощаю как только могу). Деструктор вызывается при уничтожении объекта, так что вот тут delete []pCar; он будет вызван. Но вы не написали свой деструктор, а генерируемый понятия не имеет, что делать с name и потому не делает ничего. Так что надо дописать свой деструктор - struct car { char *name; ~car() { delete[]name; } }; В таком случае будет достаточно написать ваш delete []pCar; - и все будет корректно. Почти. Почему почти? потому что а вдруг вы забудете инициализировать name? И он будет показывать непонятно куда, а при удалении непонятно чего произойти может все, что угодно. Поэтому давайте допишем конструктор car(): name(nullptr) {} И все бы ничего, но ведь кто-то может (может, даже вы) написать где-то pCar->name = "ПЦ". И при попытке освобождения будут неприятности. Поэтому лучше делать не структуру, а класс: struct car { public: car(): name(nullptr) {} ~car() { delete[]name; } private: char *name; }; Готово? Почти. Потому что надо еще как-то теперь заставить name хранить строку. Опять же, делаем конструктор car(const char * str):name(nullptr) { if (str) { name = new char[strlen(str)+1]; strcpy(name,str); } } и функцию-член для обращения к этой строке: const char* str() const { return name; } и оператор присваивания, чтоб name менять: car& operator=(const char * str) { delete[]name; if (str) { name = new char[strlen(str)+1]; strcpy(name,str); } else name = nullptr; } Вот теперь можно считать готово. Можно дописать еще многое, но главное - уже на месте: struct car { public: car(const char * str = nullptr):name(nullptr) { if (str) { name = new char[strlen(str)+1]; strcpy(name,str); } } ~car() { delete[]name; } const char str() const { return name; } car& operator=(const char * str) { delete[]name; if (str) { name = new char[strlen(str)+1]; strcpy(name,str); } else name = nullptr; } private: char *name; }; А ваш код превращается в car *pCar = new car[QCar]; for ( int i = 0; i < QCar; i++) { cout << "Car name: "; char temp [size_m]; char >> temp; pCar[i] = temp; } cout << " Your auto"<< endl; for ( int i = 0; i < QCar; i++) { cout << pCar[i].str(); } delete []pCar; Примерно так - но только для начала :)

Ответ 2



В приведенном коде есть две сущности, память под которые выделяется динамически: массив car и для каждого car - name. Необходимо освобождать память, выделенную и для того, и для другого. В текущем варианте освобождается память, выделенная для массива car. Память, выделенная для названий автомобилей не освобождается. Сделать это можно, например, так: for (int i = 0; (i < QCar); ++i) { delete[] pCar[i].name; } delete[] pCar; Однако, - для снижения вероятности ошибок и для освобождения памяти при возникновении исключений, - лучше было бы автоматизировать этот процесс: применить интеллектуальные указатели или создать свои классы с освобождающими память деструкторами (а для name можно использовать string).

Ответ 3



Исключительно о том как добавить деструктор в Car: struct car { char *name; car()//для чистоты :name(nullptr) {} ~car() { delete name; } }; А так же неплохо бы ещё и конструктор копирования, так как вы храните указатель на область памяти, которую стоит копировать при копировании объекта. А если пройтись по коду, то многое не правильно (об указателе на внешнюю по отношению к классу car память понятно -- нарушение инкапсуляции). Относительно переменной temp: Это указатель на массив данных, который создаётся в стеке итерации цикла, при этом далее вы туда копируете данные, потом этот указатель приравниваете к внутренней переменной car. После итерации цикла память (та, на которую указывала переменная temp) и name указывает в никуда -- падение при доступе или когда-то, зависит от содержимого. Так же не освобождается переменная ptemp (strcpy копируется не указатель, а содержимое). -- утечка памяти. Используйте для строк std::string, замечательный класс, который умеет всё, что вам нужно (управление памятью, копирование, разделение памяти и т.д.)

Ответ 4



for (int i = 0; i < QCar; i++) { cout << "Car name: "; char temp[size_m]; //char >> temp; char *ptemp = new char[strlen(temp) + 1]; strcpy(ptemp, temp); pCar[i].name = ptemp; delete[]ptemp; }

Ответ 5



C pCar все правильно, а вот очистить ptemp забыли. pCar[i].name = ptemp; delete[] ptemp

Комментариев нет:

Отправить комментарий