Помогни ни да направим Uroci.net по - богат! Добави урок

C++ част.5 (Свободна памет)

C++ » C++
fix3d   трудност:    видян: 19001


Глава 5: Свободна памет и презаредими имена

Съдържание на пета глава :

5.1. Разпределение на свободната памет
5.2. Един пример за свързан списък
5.3. Презаредими имена на функции
5.4. Указатели към функции
5.5. Свързване, безопасно относно типовете


Тази глава разглежда две фундаментални концепции - свободната памет за програмата и презареждането на имената на функциите. Свободната за програмата памет позволява да се отделя памет по време на изпълнение. Реализацията на класа IntArray в глава 1 ни предложи един кратък първи поглед върху този проблем. В тази глава ние ще го разгледаме подробно. Презареждането на имената на функциите позволява на няколко екземпляра на дадена функция, която предлага една обща операция, отнасяща се за аргументи от различен тип, да имат едно и също име. Ако вече сте написали поне един аритметичен израз на някой програмен език, то вие сте използвали предварително дефинирани презаредими функции. В тази глава ние ще видим как да дефинираме наши собствени такива функции.

5.1. Разпределение на свободната памет

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

IntArray
IntArray( int sz ) {
size = sz;
ia = new int[ size ];
for ( int i = 0; i < sz; ++i ) ia[ i ] = 0;
}

IntArray има даннови елемента size и ia. ia, който е указател към цяло число, ще адресира разположението на масива в свободната памет. Един от аспектите на използването на свободната памет е, че тя не е именувана. Обектите, разположени в тази памет, се обработват индиректно чрез указатели. Втори аспект на използването на свободната памет, е, че тя не е инициализирана и следователно винаги трябва да й бъде давана стойност преди употреба. Поради това е написан и цикъла for, чрез който на всеки елемент на ia се дава стойност 0. size, разбира се, съдържа размера на масива.

Свободната памет се разпределя чрез прилагане на оператора new към даден типов спецификатор, включително и към име на клас. Може да бъде отделена памет както за единичен обект, така и за масив от обекти. Например,

int *pi = new int;

отделя памет за един обект от тип int. Операторът new връща указател към този обект и чрез него се инициализира pi.

IntArray *pia = new IntArray( 1024 );

отделя памет за обекта клас IntArray. Скобите, които са записани след името на класа, ако ги има, се явяват като аргументи на конструктора на класа. В този случай, pia се инициализира като указател към обект - клас IntArray с 1024 елемента. Ако скобите липсват, както в израза

IntAarray *pia2 = new IntArray;

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

Нека даден масив е разположен в свободната памет посредством типов спецификатор със затворена в скоби размерност. Размерноста може да бъде зададена чрез произволен сложен израз. Операторът new връща указател към първият елемент на масива. Например,

#include <string.h>
char *copyStr ( const char *s ) {
char *ps = new char[ strlen(s) + 1 ];
strcpy( ps, s );
retunr ps;
}

За масивите като класови обекти може също да бъде отделяна памет. Например,

IntArray *pia = new IntArray[ someSize ];

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

Отделянето на памет по време на изпълнение се нарича динамично разпределяне на паметта. Казваме, че масивът, адресиран чрез ia, е разположен динамично. Отделянето на памет за самия указател ia, обаче, се извършва по време на компилация - това е причината, поради която ia може да бъде именуван обект. Отделянето на памет по време на компилация се нарича статично разпределяне на паметта. Казваме, че указателят ia е разположен статично.

Времето на съществуване на един обект, т.е. този период от време, когато се изпълнява програмата, се нарича период на активност на обекта. За променливите, дефинирани с файлов обхват, се казва, че притежават статичен период на активност. За тях се отделя памет преди започване на изпълнението на програмата и тя остава свързана с променливата докато програмата приключи работата си. За променливите, дефинирани с локален обхват, се казва, че притежават локален период на активност. За тях се отделя памет при всяко навлизане в локалния им обхват; на излизане от него паметта се освобождава. Всяка локална променлива static има статичен период на активност.

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

IntArray grow() разширява масива, адресиран чрез ia, с половината от размера му. Първо, трябва да бъде отделена памет за един нов по-голям масив. След това трябва да се копират стойностите на стария масив, а допълнителните елементи трябва да се инициализират със стойност 0. Накрая, паметта, заета от старият масив, трябва да се освободи явно чрез прилагане на оператора delete.

void IntArray grow() {
int *oldia = ia;
int oldSize = size;
size += size/2 + 1;
ia = new int[ size ];// copy elements of old array into new
for ( int i = 0; i < oldSize; ++i ) ia[ i ] = oldia[ i ];
for ( ; i < size; ++i ) ia[ i ] = 0;
delete oldia;
}

oldia има локален период на активност; паметта, отделена за нея, се освобождава автоматично, когато изпълнението на функцията приключи. Това не се отнася, обаче, за адресирания от oldia масив. Неговият период на активност е от динамичен тип и продължава да съществува и извън границите на локалния обхват. Ако паметта, отделена за масива, адресиран чрез oldia, не бъде освободена явно, като се използват оператора delete, тя остава присъединена към програмата. delete oldia; освобождава, отделената за масива памет.

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

IntArray *pia = new IntArray[ size ];

Тогава операторът delete, приложен към pia изглежда така

delete [size] pia;

Операторът delete трябва да бъде прилаган само за паметта, която е била отделена чрез оператора new. Прилагането на оператора delete към памет, която не е отделена от свободната памет вероятно ще се прояви в не дефинираното поведение на програмата по време на изпълнение. Прилагането на оператора delete върху указател със стойност 0, обаче, не предизвиква никакви опасни последици - т.е. към указател, който не адресира обект. Следват няколко примера за безопасно и не безопасно прилагане на оператора delete

void f(){
int i;
char *str = "dwarves";
int *pi = &i; // dangerous delete pi;
intArray *pia = 0; // dangerous delete pia;
doduble *pd = new double;
delete str;
}

Свободната памет на програмата не е безкрайна; през времето на изпълнение на програмата тя може да бъде изчерпана. (Разбира се, ако обектите, които не са необходими повече, не бъдат изтрити, ще се увеличи скоростта на изчерпване на свободната памет). По подразбиране, new връща 0, когато наличната свободна памет не е достатъчна за да удовлетвори заявката.

Програмистът не може спокойно да игнорира възможността, когато new връща 0. Нашата функция grow(), например, няма да работи ако new не е в състояние да отдели исканата памет. Да припомним, че нашият текст изглеждаше така

ia = new int[ size ]; // trouble
if new returns 0
for ( int i = 0; i < oldSize; ++i )
ia[ i ] = oldia[ i ];

Програмистът трябва да предотврати изпълнението на цикъла for, когато ia има стойност 0. Най-простият метод за да бъде направено това, е да се добави оператор за проверка на стойността на ia, който да следва обръщението към new. Например,

ia = new int[ size ];
if ( !ia ){ error("IntArray
grow()
free store exhausted");
}

където error() е една обща функция, дефинирана от програмиста и предназначена да съобщава за грешки като осигурява елегантен изход.

Ето една малка програма, която илюстрира използването на grow()

#include <stream.h>
#include "IntArray.h"

IntArray ia[ 10 ];
main() {
cout << "size " << ia.getSize() << "n";
for ( int i = 0; i < ia.getSize(); ++i )
ia[i] = i*2; // initialize ia.grow();
cout << "new size " << ia.getSize() << "n";
for ( i = 0; i < ia.getSize(); ++i )
cout << ia[i] << " ";
}

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

size 10
new size
16 0 2 4 6 7 10 12 14 16 18 0 0 0 0 0 0

Ето една функция, която е проектирана да илюстрира изчерпването на свободната памет. Тя е реализирана като рекурсивна функция, чието условие за спиране е връщането на стойност 0 от new

#include <stream.h>

viod exhaustFreeStore( unsigned long chunk ) {
static int gepth = 1;
static int report = 0;
++depth; // keep track of invocations
double *ptr = new double[ chunk ];
if ( ptr )
exhaustFreeStore( chunk );// free store exhausted
delete ptr;
if ( !report++)
cout << "Free Store Exhausted" << "tchunk " << chunk
<< "depth " << depth << "n";
}


Страници: 1 2 3 4 »

Регистрирайте се, за да добавите коментар


Калдейта ЕООД - © 2003-2010. Всички права запазени.
Препоръчваме: Национален Бизнес | Bomba.bg | IT Новини | Диплома.бг | TRAVEL туризъм | Реферати | AmAm.bg | Иде.ли | Курсови работи | Фото Форум | Spodeli.net | Фото-Култ | Atol.bg | Elmaz.com | MobileBulgaria.com | Казанлък.Com