Една програма може да съхранява информация в основната памет на компютъра по два основни начина. Първият използва глобални и локални променливи, включително масиви, структури и класове. В случай на глобални и статични локални променливи, мястото за съхранение на информацията е фиксирано за времето на изпълнение на програмата. В случай на локални променливи, паметта се разпределя в стека. Въпреки че Borland C++ обработва тези променливи много ефективно, използването им изисква програмистът да знае предварително количеството памет, което ще бъде необходимо по време на изпълнение на програмата.

Вторият начин за съхраняване на информация е използването на системата за динамично разпределение на паметта Borland C++. При този метод паметта за съхраняване на информация се разпределя от свободната област на паметта според нуждите и се връща обратно, т.е. освобождава се, когато вече не е необходимо. Областта на свободната памет се намира между областта на паметта, където се намира програмата, и стека. Тази област се нарича купчина и се използва за заявки за динамично разпределение на паметта.

Предимството на използването на динамична памет е, че една и съща памет може да се използва за съхраняване на различна информация по време на изпълнение на програмата. Тъй като паметта се разпределя за конкретна цел и се освобождава, когато използването й приключи, възможно е същата памет да се използва в друг момент за други цели в друга част на програмата. Друго предимство на динамичното разпределение на паметта е възможността за създаване на свързани списъци, двоични дървета и други динамични структури от данни с него.

Ядрото на динамичното разпределение на паметта на C са функциите malloc() и free(), които са част от стандартната библиотека. Всеки път, когато се направи заявка за разпределение на памет от функцията malloc(), се разпределя част от наличната свободна памет. Всеки път, когато тази памет се освободи с помощта на функцията free(), тази памет се връща обратно в системата.

Езикът C++ дефинира два оператора за динамично разпределение на паметта, нов и изтриване.

Стандартът ANSI C дефинира само четири функции за динамично разпределение на паметта: calloc(), malloc(), free() и realloc(). Въпреки това, Borland C++ съдържа няколко други функции за динамично разпределение на паметта. При компилиране на код за модерния 32-битов модел памет, паметта е плоска и обикновено се използват само четирите стандартни функции за разпределение на паметта.

Стандартът ANSI C уточнява, че заглавната информация, необходима за динамично разпределение на паметта, се съдържа във файла stdlib.h. Въпреки това, Borland C++ ви позволява да използвате заглавните файлове stdlib.h или alloc.h. Тук използваме заглавния файл stdlib.h, тъй като осигурява преносимост. Някои други функции за динамично разпределение на памет изискват заглавните файлове alloc.h, malloc.h или dos.h. Обърнете специално внимание кой заглавен файл е необходим, за да използвате всяка функция.

Открихме възможностите за динамично разпределение на паметта. Какво означава? Това означава, че при динамичното разпределение на паметта паметта се запазва не на етапа на компилация, а на етапа на изпълнение на програмата. И това ни дава възможност да разпределяме паметта по-ефективно, главно за масиви. ОТ динамична селекцияпамет, не е необходимо да задаваме предварително размера на масива, особено след като не винаги е известно какъв размер трябва да има масивът. След това нека да разгледаме как можете да разпределите памет.

Разпределяне на памет в C (функция malloc)

Функцията malloc() е дефинирана в заглавен файл stdlib.h, той се използва за инициализиране на указатели с необходимото количество памет. Паметта се разпределя от сектора оперативна паметдостъпни за всички програми, работещи на тази машина. Аргументът е броят байтове памет, които трябва да бъдат разпределени, функцията връща указател към разпределения блок в паметта. Функцията malloc() работи точно като всяка друга функция, нищо ново.

защото различни видоведанните имат различни изисквания към паметта, ние по някакъв начин трябва да се научим как да получаваме размера в байтове за данните различен тип. Например, имаме нужда от част от паметта за масив от стойности от тип int - това е един размер на паметта и ако трябва да разпределим памет за масив със същия размер, но вече от тип char - това е различен размер. Следователно трябва по някакъв начин да изчислите размера на паметта. Това може да стане с операцията sizeof(), която взема израз и връща неговия размер. Например sizeof(int) ще върне броя байтове, необходими за съхраняване на int стойност. Помислете за пример:

#включи int *ptrVar = malloc(sizeof(int));

В този пример, в ред 3на указателя ptrVar се присвоява адресът на част от паметта, чийто размер съответства на типа данни int. Автоматично тази област от паметта става недостъпна за други програми. А това означава, че след като заделената памет стане ненужна, тя трябва изрично да се освободи. Ако паметта не е изрично освободена, тогава в края на програмата паметта няма да бъде освободена за операционна система, това се нарича изтичане на памет. Можете също така да определите размера на разпределената памет, която трябва да бъде разпределена, като подадете нулев указател, ето пример:

int *ptrVar = malloc(sizeof(*ptrVar));

Какво става тук? Операцията sizeof(*ptrVar) ще оцени размера на областта на паметта, към която сочи указателят. Тъй като ptrVar е указател към част от паметта от тип int, sizeof() ще върне размера на цяло число. Тоест всъщност според първата част от дефиницията на указателя се изчислява размерът на втората част. И така, защо ни трябва? Това може да е необходимо, ако внезапно се наложи да промените дефиницията на указател, int, например, на float и тогава не е необходимо да променяме типа данни в две части от дефиницията на указателя. Ще бъде достатъчно да променим първата част:

Float *ptrVar = malloc(sizeof(*ptrVar));

Както можете да видите, в такъв запис има един много форте, не трябва да извикваме функцията malloc() с помощта на sizeof(float) . Вместо това, ние предадохме указател към типа float на malloc(), в който случай размерът на разпределената памет ще се определи автоматично от само себе си!

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

Float *ptrVar; /* . . . сто реда код */ . . . ptrVar = malloc(sizeof(*ptrVar));

Ако сте използвали конструкцията за разпределяне на памет с операцията sizeof(), тогава ще трябва да намерите дефиницията на указателя в кода, да погледнете неговия тип данни и едва тогава ще можете да разпределите правилно паметта.

Освобождаване на разпределената памет

Освобождаването на памет става с функцията free(). Ето един пример:

Безплатно (ptrVar);

След освобождаване на паметта е добра практика да нулирате указателя на нула, т.е. да зададете *ptrVar = 0 . Ако присвоите 0 на указател, указателят става нула, с други думи, вече не сочи никъде. Винаги след освобождаване на паметта присвоявайте 0 на показалеца, в противен случай, дори след освобождаване на паметта, показалецът все още сочи към нея, което означава, че можете случайно да навредите на други програми, които могат да използват тази памет, но вие дори не знаете нищо за това ще знаете и ще мислите, че програмата работи правилно.

P.S.: Всеки, който обича редактирането на видео, може да се заинтересува от този видеоредактор за Windows 7. Видео редакторът се нарича Movavi, може би някой вече е запознат с него или дори е работил с него. С тази програма на руски можете лесно да добавяте видео от камерата, да подобрявате качеството и наслагването красиви видеоклиповеефекти.

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

Има два вида статични променливи:

  • глобални променливиса дефинирани променливи външни функции, в чието описание липсва думата static. Обикновено описанияглобалните променливи, които включват думата extern, се преместват в заглавни файлове (h-файлове). Думата extern означава, че променливата е декларирана, но не е създадена на този етап в програмата. Определенияглобални променливи, т.е. описания без думата extern се поставят във файлове за изпълнение (c-файлове или cpp-файлове). Пример: глобалната променлива maxind се декларира два пъти:
    • в h-файл с линията

      extern int maxind;

      тази декларация отчита наличието на такава променлива, но не създава тази променлива!
    • в cpp файл с линията

      int maxind = 1000;

      това описание създавапроменлива maxind и й присвоява първоначална стойност 1000. Обърнете внимание, че езиковият стандарт не изисква задължително присвояване на първоначални стойности на глобални променливи, но въпреки това винаги е по-добре да направите това, в противен случай променливата ще съдържа непредсказуема стойност (боклук, както казват програмистите). Добър стил е да инициализирате всички глобални променливи, когато са дефинирани.
    Глобалните променливи са наречени така, защото са достъпни навсякъде в програмата във всички нейни файлове. Следователно имената на глобалните променливи трябва да са достатъчно дълги, за да се избегнат случайни имена на две различни променливи. Например имената x или n за глобална променлива не са подходящи;
  • статични променливиса променливи, чието описание съдържа думата static. По правило се декларират статични променливи външни функции. Такива статични променливи са като глобални променливи по всякакъв начин, с едно изключение: обхватът на статичната променлива е ограничен до един файл, в който е дефинирана - и освен това може да се използва само след като е била декларирана, т.е. по-долу в текста. Поради тази причина декларациите на статичните променливи обикновено се поставят в началото на файла. За разлика от глобалните променливи, статичните променливи никогане са описани в h-файлове (външни и статични модификатори са в конфликт помежду си). Съвет: използвайте статични променливи, ако искате те да са достъпни само за функциите, описани вътре същият файл. Ако е възможно, не използвайте глобални променливи в такива ситуации, това ще избегне конфликти на имена при изпълнение на големи проекти, състоящи се от стотици файлове.
    • Статичната променлива може също да бъде декларирана във функция, въпреки че обикновено никой не прави това. Променливата не се намира в стека, а в статичната памет, т.е. не може да се използва в рекурсия и стойността му се запазва между различните входове на функцията. Обхватът на такава променлива е ограничен до тялото на функцията, в която е дефинирана. В противен случай тя е подобна на статична или глобална променлива. забележи това ключова дума static в C се използва за две различни цели:
      • като индикация за типа памет: променливата се намира в статична памет, а не в стека;
      • като начин за ограничаване на обхвата на променлива в рамките на един файл (в случай на декларация на променлива извън функция).
  • Думата static може също да се появи в заглавката на функция. Въпреки това, той се използва само за ограничаване на обхвата на името на функцията до един файл. Пример:

    статичен int gcd(int x, int y); // Прототип на функция. . . static int gcd(int x, int y) ( // Реализация. . . )

    Съвет: използвайте статичен модификаторв заглавката на функцията, ако е известно, че функцията ще бъде извикана само в рамките на един файл. Думата static трябва да присъства както в описанието на прототипа на функцията, така и в заглавната част на функцията, когато се внедрява.

Стекова или локална памет

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

Локалните променливи могат да се използват в рекурсия, защото когато функцията се въведе отново, в стека се създава нов набор от локални променливи и предишният набор не се унищожава. По същата причина локалните променливи са безопасни при използване на нишки в паралелно програмиране (вижте раздел 2.6.2). Програмистите наричат ​​това свойство на функция повторно влизане, от английски. re-enter able - възможност за повторно влизане. Това е много важно качество по отношение на надеждността и безопасността на програмата! Програма, която работи със статични променливи, няма това свойство, така че за да защитите статичните променливи, трябва да използвате механизми за синхронизация(вижте 2.6.2), а логиката на програмата става много по-сложна. Винаги трябва да избягвате използването на глобални и статични променливи, ако можете да се справите с локални.

Недостатъците на локалните променливи са продължение на техните предимства. Локалните променливи се създават, когато функция влезе и изчезват, когато излезе, така че не могат да се използват като данни, споделяни между множество функции. В допълнение, размерът на хардуерния стек не е безкраен, стекът може да препълни в един фин момент (например по време на дълбока рекурсия), което ще доведе до катастрофално прекратяване на програмата. Следователно локалните променливи не трябва да имат голям размер. По-специално, големи масиви не могат да се използват като локални променливи.

Динамична памет или куп

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

Пространство, разпределено за динамична памет виртуална паметпроцес между статичната памет и стека. (Механизмът на виртуалната памет беше обсъден в раздел 2.6.) Обикновено стекът се намира на по-високи адреси на виртуалната памет и расте в посока на намаляване на адресите (вижте раздел 2.3). Програмата и константните данни са поставени на по-ниски адреси, статичните променливи са разположени по-горе. Пространството над статичните променливи и под стека е заето от динамична памет:

адрес съдържание на паметта

програмен код и данни,

защитени от промяна

...

статични променливи

програми

динамична памет

макс. адрес (2 32 -4)

стек

Структурата на динамичната памет се поддържа автоматично от системата за изпълнение на C или C++. Купчината се състои от уловени и свободни сегменти, всеки предшестван от сегментен дескриптор. При изпълнение на заявка за захващане на паметта, изпълняващата система търси свободен сегмент с достатъчен размер и хваща сегмент с необходимата дължина в него. Когато се освободи сегмент от паметта, той се маркира като свободен; ако е необходимо, няколко последователни свободни сегмента се комбинират.

Езикът C използва стандартните функции malloc и free за придобиване и освобождаване на динамична памет; техните прототипи са описани в стандартния заглавен файл "stdlib.h". (Името malloc е съкращение от разпределение на паметта- "улавяне на паметта".) Прототипите на тези функции изглеждат така:

void *malloc(size_t n); // Грабване на част от паметта // n байта с размер void free(void *p); // Освобождаване на областта // от памет с адрес p

Тук n е размерът на заснетата област в байтове, size_t е името на един от типовете цели числа, които дефинират максимален размерпревзета зона. Типът size_t е указан в стандартния заглавен файл " stdlib.h " с помощта на израза typedef (вижте стр. 117). Това гарантира, че текстът на C програмата е независим от използваната архитектура. При 32-битова архитектура size_t се дефинира като цяло число без знак:

typedef unsigned int size_t;

Функцията malloc връща адреса на уловената област от паметта или нула при повреда (когато няма достатъчно голяма свободна област). Безплатната функция освобождава част от паметта на дадения адрес. За задаване на адреса се използва указател. общ типпразнота*. След извикване на функцията malloc, тя трябва да бъде преобразувана към указател към конкретен тип с помощта на операцията преобразуване, вижте раздел 3.4.11. Например, следният пример хваща 4000-байтова купчина и присвоява неговия адрес на указател към масив от 1000 цели числа:

int*a; // Указател към масив от цели числа. . . a = (int *) malloc(1000 * sizeof(int));

Изразът в аргумента на функцията malloc е 4000, тъй като размерът на цялото число sizeof(int) е четири байта. За да конвертирате указател, използвайте операцията за преобразуване (int *) от указател от общ тип към указател към цяло число.

Пример: отпечатване на първите n прости числа

Нека да разгледаме пример, който използва улавяне на динамична памет. Трябва да въведете цяло число n и да отпечатате първите n прости числа. (Просто число е число, което няма нетривиални делители.) Използваме следния алгоритъм: проверяваме последователно всички нечетни числа, започвайки с три (разглеждаме две отделно). Разделяме следващото число на всички прости числа, намерени в предишните стъпки на алгоритъма и не надвишаващи квадратния корен на проверяваното число. Ако не се дели на нито едно от тези прости числа, то самото то е просто; той се отпечатва и добавя към масива от намерени прости числа.

Тъй като необходимият брой прости числа n не е известен преди стартирането на програмата, не е възможно да се създаде масив, който да ги съхранява в статична памет. Изходът е да заграбите място за масив в динамичната памет още след въвеждане на числото n. Ето и пълния текст на програмата:

#включи #включи #включи int main() ( int n; // Необходим брой прости числа int k; // Текущ брой намерени прости числа int *a; // Указател към масив от намерени прости числа int p; // Следващ номер за проверка int r; // Цяла част квадратен корен от p int i; // Индекс на прост делител bool prime; // Прост прост printf("Въведете брой прости числа: "); scanf("%d", &n); if (n<= 0) // Некорректное значение =>връщане 1; // изход с код за грешка // Захващане на памет за масив от прости числа a = (int *) malloc(n * sizeof(int)); а = 2; k = 1; // Добавяне на 2 към масива printf("%d ", a); // и го отпечатайте p = 3; докато (к< n) { // Проверяем число p на простоту r = (int)(// Целая часть корня sqrt((double) p) + 0.001); i = 0; prime = true; while (i < k && a[i] <= r) { if (p % a[i] == 0) { // p делится на a[i] prime = false; // =>p не е просто, прекъсване; // излизане от цикъла ) ++i; // Към следващия прост делител ) if (prime) ( // Ако намерите просто число, a[k] = p; // след това го добавете към масива ++k; // Увеличете броя на простите числа printf( "%d ", p); // Отпечатване на просто число if (k % 5 == 0) ( // Превключване към нова линия printf("\n"); // след всеки пет числа ) ) p += 2; // Към следващото нечетно число) if (k % 5 != 0) ( printf("\n"); // Превеждане на низ) // Свободна динамична памет free(a); връщане 0; )

Пример за това как работи тази програма:

Въведете числото на простото: 50 2 3 5 7 11 13 13 19 29 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 167 173 179 181 191 193 193 197 197 197 197 197 197 197 197 197 197 197 197 197 197 199 211 223 227 229 Следваща

C++ нови и изтриващи оператори

Езикът C++ използва операторите new и delete за придобиване и освобождаване на динамична памет. Те са част от езика C++, за разлика от функциите malloc и free, които са част от стандартната библиотека на C.

Нека T е някакъв C или C++ тип, p е указател към обект от тип T. След това, за улавяне на памет с размер на един елемент от тип T, се използва операторът new:

T*p; p = нов T;

Например, за да заснемете осем байта под реално числоизползва се фрагмент от тип double

двойно *p; p = ново двойно;

Когато използвате new, за разлика от malloc, не е необходимо да прехвърляте указател от void* към желания тип: операторът new връща указател към типа, написан след думата new. Сравнете два еквивалентни фрагмента в C и C++.

    съхранява глобални променливи и константи;

    размерът се определя по време на компилиране.

    стек

    съхранява локални променливи, функционални аргументи и междинни стойности на изчисленията;

    размерът се определя при стартиране на програмата (обикновено се разпределят 4 MB).

    Купчина

    динамично разпределена памет;

    ОС разпределя памет на части (според необходимостта).

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

Динамичната памет, наричана още "куп", се разпределя изрично по искане на програма от ресурсите на операционната система и се контролира от указател. Той не се инициализира автоматично и трябва изрично да бъде освободен. За разлика от статичната и автоматичната памет, динамичната памет е практически неограничена (ограничена само от размера на RAM) и може да се променя, докато програмата работи.

Работа с динамична памет в s

За работа с динамична памет на езика C се използват следните функции: malloc, calloc, free, realloc. Нека ги разгледаме по-подробно.

    Разпределение (извличане на памет): void *malloc(size_t size);

Като входен параметърфункцията взема размера на паметта, която трябва да бъде разпределена. Върнатата стойност е указател към разпределена част от паметта. Ако операционната система не може да разпредели памет (например няма достатъчно памет), тогава malloc връща 0.

    След като приключите работата с динамично разпределената памет, трябва да я освободите. За тази цел се използва безплатна функция, който връща памет под управление на ОС: void free(void *ptr);

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

Пример:// заделяне на памет за 1000 int елемента

int * p = (int *) malloc(1000*sizeof(int));

if (p==NULL) out<< "\n память не выделена";

безплатно (p); // връщане на паметта в купчината

2. Разпределение (улавяне на памет): void *calloc(size_t nmemb, size_t size);

Функцията работи подобно на malloc, но се различава по синтаксис (вместо размера на разпределената памет, трябва да посочите броя на елементите и размера на един елемент) и по това, че разпределената памет ще бъде нулирана. Например, след изпълнение на int * p = (int *) calloc(1000, sizeof(int)) p ще сочи към началото на int масив от 1000 елемента, инициализирани на нула.

3. Промяна на размера на паметта: void *realloc(void *ptr, size_t size);

Функцията променя размера на разпределената памет (посочена от ptr,получено от повикване malloc, calloc или realloc). Ако размерът, посочен в параметъра размерпо-голяма от тази, разпределена под показалеца ptr,след това се проверява дали е възможно да се разпределят липсващите клетки от паметта в ред с вече разпределените. Ако няма достатъчно място, тогава се разпределя нова част от паметта с размера размери данни за указател ptrсе копират в началото на новия раздел.

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

    Указател (към динамична област на паметта) се дефинира като обект на локална автоматична памет. В този случай разпределената памет няма да бъде достъпна при излизане от блока за локализиране на указателя и трябва да бъде освободена преди излизане от блока.

( int* p= (int *) calloc(n, sizeof(int))

безплатно (p); // безплатен дин. памет

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

(статично int* p = (int *) calloc(n, sizeof(int));

p= (int *) calloc(n, sizeof(int));

f(50); //маркирайте din. памет за освобождаване

f1(100); //маркирайте din. памет (първи достъп)

f1(100); // работа с din. памет

f1(0); // безплатен дин. памет

    Указателят е глобален обект по отношение на блока. Динамичната памет е налична във всички блокове, където показалецът е "видим". Паметта трябва да се освобождава само когато вече не се използва.

int*pG; //работещ указател за din. памет (глобална променлива)

void init (int размер)

за (i=0; i< size; i++) //цикл ввода чисел

( printf("x[%d]=",i);

scanf("%d", &pG[i]);

int сума (int размер)

за (i=0; i< size; i++) //цикл суммирования

// разпределение на паметта

pG= (int *) calloc(n, sizeof(int));

// работа с динамична памет

printf(\ns=%d\n",сума(n));

свободен (pG); pG=NULL; // освобождаване на памет

Работа с динамична памет в C++

C++ има собствен механизъм за разпределяне и освобождаване на памет - това са функции нови Изтрий.Пример за употреба нов: int * p = ново int; // разпределяне на памет за 1000 e-ths Т.е. когато използвате функцията новняма нужда да хвърляте показалец и няма нужда да използвате размер на().Освобождаване на селекция с новпаметта се обработва от следното извикване: delete p; Ако трябва да разпределите памет за един елемент, тогава можете да използвате int * q = new int; или int * q = new int(10); // разпределеното int ще бъде инициализирано със стойност 10, в този случай изтриването ще изглежда така: delete q;

C++ поддържа три основни типа разпределяне(или по "разпределения") памет, две от които вече сме запознати:

Разпределение на статична паметважи за и променливи. Паметта се разпределя веднъж, в началото на програмата, и се запазва през цялата програма.

Автоматично разпределение на паметтасе извършва за и . Паметта се разпределя, когато се влезе в блока, съдържащ тези променливи, и се премахва, когато се излезе от него.

Динамично разпределение на паметтае темата на този урок.

Динамично разпределение на променливи

И статичното, и автоматичното разпределение на паметта имат две общи свойства:

Как работи динамичното разпределение на паметта?

Вашият компютър има памет (може би голяма част от нея), която е достъпна за използване от програми. Когато стартирате програма, вашата операционна система зарежда тази програма в някаква част от тази памет. И тази памет, използвана от вашата програма, е разделена на няколко части, всяка от които изпълнява определена задача. Едната част съдържа вашия код, другата се използва за извършване на нормални операции (проследяване на извикани функции, създаване и унищожаване на глобални и локални променливи и т.н.). Ще говорим за това по-късно. По-голямата част от наличната памет обаче просто е там и чака заявки за разпределение от програми.

Когато динамично разпределяте памет, вие молите операционната система да запази част от тази памет, която да използва вашата програма. Ако ОС може да изпълни тази заявка, тогава адресът на тази памет се връща обратно към вашата програма. Отсега нататък вашата програма ще може да използва тази памет както пожелае. Когато вече сте направили всичко необходимо с тази памет, тогава тя трябва да бъде върната обратно в операционната система за разпространение между други заявки.

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

Освобождаване на памет

Когато динамично разпределяте променлива, можете също да я инициализирате с или унифицирана инициализация (в C++11):

int *ptr1 = нов int(7); // използване на директна инициализация int *ptr2 = new int ( 8 ); // използване на унифицирана инициализация

Когато всичко, което е необходимо, вече е направено с динамично разпределена променлива, трябва изрично да кажете на C++ да освободи тази памет. За променливите това се прави с оператор Изтрий:

// Да предположим, че ptr вече е разпределен с нов изтриване на ptr; // връщане на паметта, към която сочи ptr обратно към операционната система ptr = 0; // направи ptr null (използвайте nullptr вместо 0 в C++11)

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

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

Имайте предвид, че изтриването на указател, който не сочи към динамично разпределена памет, може да доведе до проблеми.

висящи указатели

C++ не дава гаранции за това какво се случва със съдържанието на освободената памет или със стойността на указателя, който се изтрива. В повечето случаи паметта, върната на операционната система, ще съдържа същите стойности, които е имала преди освобождаване, а показалецът ще продължи да сочи само към вече освободената (изтрита) памет.

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

#включи int main() ( int *ptr = new int; *ptr = 8; // поставя стойността в разпределеното място в паметта delete ptr; // връща паметта обратно на операционната система. ptr вече е висящ указател std:: cout<< *ptr; // разыменование висячего указателя приведёт к неожиданным результатам delete ptr; // попытка освободить память снова приведёт к неожиданным результатам также return 0; }

#включи

int main()

int * ptr = нов int; // динамично разпределяне на целочислена променлива

* ptr = 8; // поставяне на стойността в разпределената памет

изтриване на ptr; // връщане на паметта обратно към операционната система. ptr вече е висящ указател

std::cout<< * ptr ; // дереферирането на висящ указател ще доведе до неочаквани резултати

изтриване на ptr; // опитът за освобождаване на памет отново ще доведе до неочаквани резултати

връщане 0;

В програмата по-горе стойността 8, която преди това е била присвоена на динамична променлива, може или не може все още да е там, след като бъде освободена. Също така е възможно освободената памет вече да е била разпределена за друго приложение (или за собствена употреба на операционната система) и опитът за достъп до нея ще накара операционната система автоматично да прекрати вашата програма.

Процесът на освобождаване на памет също може да доведе до създаване няколковисящи указатели. Разгледайте следния пример:

#включи int main() ( int *ptr = new int; // динамично разпределяне на целочислена променлива int *otherPtr = ptr; // otherPtr сега сочи към същата разпределена памет като ptr delete ptr; // връща паметта обратно към операционната система ptr и otherPtr вече са висящи указатели ptr = 0; // ptr вече е nullptr // Въпреки това otherPtr все още е висящ указател! return 0; )

#включи

int main()

int * ptr = нов int; // динамично разпределяне на целочислена променлива

int * otherPtr = ptr; // otherPtr сега сочи към същата разпределена памет като ptr

изтриване на ptr; // връщане на паметта обратно към операционната система. ptr и otherPtr вече са висящи указатели

ptr = 0; // ptr вече е nullptr

// Обаче otherPtr все още е висящ указател!

връщане 0;

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

Второ, когато изтриете указател и ако той не излезе веднага след изтриването, тогава той трябва да бъде направен нулев, т.е. присвоете стойност 0 (или в C++11). Под „извън обхват веднага след изтриване“ имам предвид, че изтривате указателя в самия край на блока, в който е деклариран.

Правило: Задайте изтритите указатели на 0 (или nullptr в C++11), освен ако не излязат извън обхвата веднага след изтриването.

нов оператор

Когато паметта е поискана от операционната система, в редки случаи тя може да не е налична (т.е. може да не е налична).

По подразбиране, ако новият оператор не работи, паметта не е разпределена, тогава a изключение лошо_разпределение. Ако това изключение бъде обработено неправилно (което ще бъде, тъй като все още не сме разгледали изключенията и обработката им), тогава програмата просто ще прекъсне (срине) с грешка при необработено изключение.

В много случаи процесът на хвърляне на изключение с new (както и срив на програмата) е нежелан, така че има алтернативна форма на new, която връща нулев указател, ако паметта не може да бъде разпределена. Просто трябва да добавите std::nothrow константамежду новата ключова дума и типа данни:

int *стойност = ново (std::nothrow) int; // стойността на указателя ще стане нула, ако динамичното разпределение на целочислена променлива е неуспешно

В примера по-горе, ако new не върне указател с динамично разпределена памет, тогава ще бъде върнат нулев указател.

Дереферирането му също не се препоръчва, тъй като това ще доведе до неочаквани резултати (най-вероятно до срив в програмата). Следователно най-добрата практика е да проверите всички заявки за разпределяне на памет, за да се уверите, че тези заявки са изпълнени успешно и паметта е разпределена:

int *стойност = ново (std::nothrow) int; // искане за заделяне на динамична памет за целочислена стойност if (!value) // обработка на случая, когато new връща null (т.е. не е разпределена памет) ( // Обработване на този случай std::cout<< "Could not allocate memory"; }

Тъй като неразпределянето на памет от новия оператор е изключително рядко, програмистите обикновено забравят да направят тази проверка!

Нулеви указатели и динамично разпределение на паметта

Нулевите указатели (указатели със стойност 0 или nullptr) са особено полезни при динамично разпределение на паметта. Тяхното присъствие, така да се каже, ни казва: "Не е разпределена памет за този указател." И това от своя страна може да се използва за извършване на условно разпределение на паметта:

// Ако на ptr все още не е разпределена памет, разпределете я, ако (!ptr) ptr = new int;

Премахването на нулевия указател не засяга нищо. Така че не е необходимо следното:

if (ptr) изтриване на ptr;

ако (ptr)

изтриване на ptr;

Вместо това можете просто да напишете:

изтриване на ptr;

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

Изтичане на памет

Динамично разпределената памет няма обхват, т.е. той остава разпределен, докато не бъде изрично освободен или докато вашата програма прекрати изпълнението си (и операционната система сама изчисти всички буфери на паметта). Въпреки това, указателите, използвани за съхраняване на динамично разпределени адреси на паметта, следват правилата на нормалния обхват на променливата. Това несъответствие може да предизвика интересно поведение. Например:

void doSomething() ( int *ptr = new int; )