Loading...

Във форума е въведено ограничение, което позволява на потребителите единствено да разглеждат публикуваните въпроси.

Jovanna avatar Jovanna 186 Точки

живот на променлива, създадена чрез динамично заделена памет

Здравейте,

Защо като се излезе от скоуба, да се изтрива copy, нали сме го заделили динамично с ctor-a?  Заделена с new памет не живее ли докато решим да я освободим? (примерът е от класа Array) 

Array(size_t size) : data(new T[size]), size(size) {}

Array<T>& operator=(const Array<T>& other) {

             Array<T> copy(other);

             delete[] this->data;

             this->data = copy.data;

             this->size = copy.size;

             return *this;

       }

Примерно, ползваме функции за заделяне на динамична и масивът не се изтрива автоматично при излизане от скоуба:

int* allocateMem(int size) {

   return new int[size];

}

Поздрави!

Тагове:
0
C++ Programming 08/11/2018 20:55:50
MartinBG avatar MartinBG 4803 Точки
Best Answer

Да, памет заделена с new, се освобождава само с delete.

 

Предполагам, питаш за тази локално дефинирана променлива в copy-assignment оператора?

Array<T> copy(other);

 

Примерът, който си дала е непълен (липсват десруктор и копи-конструктор), но това, което става в Array<T>& operator=(const Array<T>& other) е следното (по редове):

 

(1) Array<T> copy(other);  // извиква се копи-конструктора на Array<T>, като му се подава other и се създава локалната променлива copy. Приемаме, че копи-конструкторът е кадърно написан и прави дийп копи на данните.

 

(2) delete[] this->data; // освобождаваме текущо заделената динамична памет за data

 

(3) this->data = copy.data; // this->data (Т* , т.е. пойнтър!!!) сега сочи към паметта, сочена и от copy.data

 

(4) this->size = copy.size; // сетваме this->size да е еквивалетно на copy.size. size е примитивен тип (size_t), т.е. стойноста се копира и операцията е безопасна

 

(5) return *this; // връщаме референция към текущия обект

 

(6) } // Методът приключва своята работа и всички локално дефинирани променливи включително и тези, дошли като параметри при извикването на метода, следва да бъдат унищожени.

В този пример имаме един входен параметър, който е константна референция (const Array<T>& other), която директно се освобождава без да променя обекта, който сочи.

По-интересно е какво се случва с локалната променлива copy, дефинирана на първи ред.

Типът ѝ е Array<T>. Когато инстанция на даден обект излезе от скоуп, се вика деструкторът на класа, към който принадлежи този обект (както и десрукторите на всички класове, които наследява, както и десрукторите на всички полета, които има). Кадърно написан деструктор за подобен клас би следвало да освободи всички ресурси, които обекта ползва, включително и динамичната памет, сочена от Т* data. Това от своя страна ще инвалидира и стойността на this->data (ред 3), защото двете сочат към един и същ адрес в паметта. Начин да се избегне този проблем в конкретният пример, е да се добави следната команда след ред (3):

copy.data = nullptr; // copy.data вече не сочи към паметта, ползвана и от this->data и по този начин предотвратяваме освобождаването ѝ.

3
09/11/2018 04:06:56
Jovanna avatar Jovanna 186 Точки

Благодаря за отговора. Но защо в двата примера по-долу има разлика в поведението за изтриване? Да разбирам ли, че когато променлива е създадена с динамично заделена памет,  тя няма да се изтрие при излизане от скоуп, защото не е обект и няма деструктор, който да я разпознае като автоматична променлива и да я изтрие?

ПРИМЕР 1:  class Arr { int* data;  ...  ~Arr() {delete[] this->data;  ;}

Arr& Arr::Foo() {

             int * nextArr = new int [5];  

             delete[] this->data;

             this->data = nextArr;

            // nextArr = nullptr; Тук необходимо ли е в този случай да се пренасочва пойнтера, след като не е обект и деструкторът няма да го "засече"?

             return *this;

       }

ПРИМЕР 2: функция в глобалното за заделяне на динамична памет. Масивът не се изтрива автоматично при излизане от скоупа:

int* allocateMem(int size) {

   int * newArr = new int [size];  

   return newArr;

}

int main() { int * myArr = allocateMem(9);  ... ... }

Поздрави!

0
MartinBG avatar MartinBG 4803 Точки

Пример 1 - няма нужда да сетваш nextArr на nullptr, но дори и да го направиш нищо няма да се промени, тъй като след излизане от скоуп nextArr просто ще изчезне, но без да се трие паметта, сочена от него. Заделената памет в този случай ще се изтрие от деструктора на обекта, за който е извикан Foo() метода, когато този обект излезе от скоуп.

 

Пример 2 - allocateMem само алокира памет и връща пойнтър към нея. Грижата за освобождаване на тази памет е на този, който извика метода. В случая, това се случва в main() и заделената памет памет се сочи от myArr, т.е. в даден момент по-нататък в кода трябва да има delete[] myArr за да се предотврати лийкване на памет.

2
10/11/2018 18:25:32
Jovanna avatar Jovanna 186 Точки

Пост2/Пример 1 : Какво означава: "... след излизане от скоуп nextArr просто ще изчезне, но без да се трие паметта, сочена от него" ?

и още две ситуации за изясняване :-)

Пост3/СИТ1/ Приемаме че имаме деструктор, разписан както трябва , но имаме дифолтен copy-assignment operator= и дифолтен copy-ctor. 

Правилна ли е следната логика?: other пристига по референция; на copy полето с data сочи към data на оригинала. Пренасочваме и this->data да сочи към data на оригинала. Скоупът се затваря и се делийтва с деструктора copy, т.е., един от пойнтерите сочещи към data на оригинала бива изтрит (copy.data),  но остават два пойнтера, които сочат към оригиналната data: на оригиналният обект other  и на *this:

class Array { int* data; int size; ...

Array<T>& operator=(const Array<T>& other) {

             Array<T> copy(other);

             delete[] this->data;

             this->data = copy.data;

             this->size = copy.size;

             return *this;

       }

Пост3/СИТ2/ Коректно ли е написан кода (имаме правилни TheBig3):

       Array<T>& operator=(Array<T> other) {  //Получаваме копие на other        

             delete[] this->data;

             this->data = other.data;  

             this->size = other.size;  

             other.data = nullptr;  //Тук предполагам, трябва да има това пренасочване към nullptr?

             return *this;

       }     

Поздрави!

0
11/11/2018 14:58:17
MartinBG avatar MartinBG 4803 Точки

1 - nextArr e пойнтър (променлива, която в себе си съдържа адрес на нещо в паметта), който "живее" в стека и просто ще бъде изтрит след като излезе от скоуп. Адреса, към който сочи, няма да бъде модифициран.

 

2 - Не съвсем. Фокусираш се върху живота на пойнтърите, но не това е важното в случая. Както писах и горе, пойнтърите са само адреси (число) към нещо в паметта. Грижа на програмиста е да осигури правилното менажиране на паметта, сочена от пойнтърите. Дефолтни copy-assignment и copy-constructor биха нарушили правилото на 3-те за тази задача, защото когато бъдат извикани, ще имаме две инстанции, които да сочат към една и съща data в паметта. 

copy-assignment примера, който си дала не е дефолтен по своята същност.

Като първа забележка му липсва проверката дали this != &other, смисълът на която е да ни пази от изтриване на паметта, когато напишем myObj = myObj;

Получаваме обекта по референция, правим му копие - ако copy-constructor е дефолтен, то и двете инстанции ще сочат към една и съща data в паметта, после освобождаваме текущата памет и пренасочваме this->data да сочи към същата data като copy (и като other при дефолтен copy-constructor). При излизане от метода ще се извика деструктора на copy, който ще освободи паметта, сочена от copy.data, this->data и other.data (при дефолтен copy-constructor). T.e. - да, остават ни два пойнтъра, които да сочат към оригиналната data, но самата data вече я няма и паметта, към която пойнтърите сочат вече не им принадлежи.

 

3 - Да, този copy-assignment оператор е коректен

2
11/11/2018 22:48:51
Jovanna avatar Jovanna 186 Точки

Пост3/СИТ1/ твой отговор: 2 - Не съвсем. (..)  "При излизане от метода ще се извика деструктора на copy, който ще освободи паметта, сочена от copy.data, this->data и other.data (при дефолтен copy-constructor). T.e. - да, остават ни два пойнтъра, които да сочат към оригиналната data, но самата data вече я няма и паметта, към която пойнтърите сочат вече не им принадлежи."

Защо при дефолтен ctor да се освободят и трите пойнтера - защо ще освободи паметта, сочена от copy.data, this->data и other.data ? 

Примерно:  int num = 123; int * ptr1 = &num; int * ptr2 = ptr1; delete ptr1;  Нали остава един пойнтер, който дочи към данните (ptr2)? Защо  данните да ги няма вече?

Поздрави!  

0
11/11/2018 23:46:25
MartinBG avatar MartinBG 4803 Точки

Този пример не е много удачен, защото пойнтърите са към памет, заделена в стека и принадлежаща на локална променлива (num), която памет следва да бъде освободена едва след като num излезе от скоуп, но дори и така, програмата ще гръмне при опит за дереференциране на ptr2 след delete на ptr1.

 

Следният пример е по-удачен за илюстриране на проблема, който изглежда ти убягва в тази серия от въпроси:
 

int* ptr1 = new int(123); 
int * ptr2 = ptr1; 
std::cout << *ptr1 << std::endl; // 123
std::cout << *ptr2 << std::endl; // 123
delete ptr1;
std::cout << *ptr2 << std::endl; // random number

 

Няма значение колко пойнтъра сочат към дадена памет - броят им не гарантира валидността на паметта.

За да освободим тази памет е достатъчно да извикаме delete през един от пойнтърите, което автоматично оставя другите пойнтъри да сочат към памет, която вече се използва от системата за други неща.

 

Представи си го така:

Имаме бомба (заделена памет), свързана с 5 независими детонатора (пойнтъри). Всеки детонатор може да взриви бомбата и това състояние ще е валидно до момента, в който някой от тях не бъде активиран (delete). От този момент нататък, бомбата (паметта) вече не съществува и останалите детонатори (пойнтъри) няма как да бъдат използвани за да я активират (достъпят).

2
12/11/2018 03:36:28
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.