Program může ukládat informace do hlavní paměti počítače dvěma hlavními způsoby. První z nich používá globální a lokální proměnné, včetně polí, struktur a tříd. V případě globálních a statických lokálních proměnných je místo uložení informací pevné po dobu provádění programu. V případě lokálních proměnných je paměť alokována na zásobníku. Přestože Borland C++ s těmito proměnnými nakládá velmi efektivně, jejich použití vyžaduje, aby programátor předem věděl, kolik paměti bude během provádění programu potřeba.

Druhým způsobem ukládání informací je použití systému dynamického přidělování paměti Borland C++. Při tomto způsobu je paměť pro ukládání informací alokována z volné paměťové oblasti podle potřeby a vrácena zpět, tzn. uvolněn, když již není potřeba. Oblast volné paměti leží mezi oblastí paměti, kde se nachází program, a zásobníkem. Tato oblast se nazývá halda a používá se pro požadavky na dynamickou alokaci paměti.

Výhodou použití dynamické paměti je, že stejnou paměť lze použít k uložení různých informací během provádění programu. Vzhledem k tomu, že paměť je alokována pro určitý účel a uvolňována, když je její použití ukončeno, je možné použít stejnou paměť v jiném okamžiku pro jiné účely v jiné části programu. Další výhodou dynamické alokace paměti je možnost s ní vytvářet propojené seznamy, binární stromy a další dynamické datové struktury.

Jádrem dynamické alokace paměti C jsou funkce malloc() a free(), které jsou součástí standardní knihovny. Kdykoli je pomocí funkce malloc() vytvořen požadavek na přidělení paměti, je přidělena část dostupné volné paměti. Kdykoli je tato paměť uvolněna pomocí funkce free(), je tato paměť vrácena zpět do systému.

Jazyk C++ definuje dva dynamické operátory alokace paměti, new a delete.

Standard ANSI C definuje pouze čtyři funkce dynamického přidělování paměti: calloc(), malloc(), free() a realloc(). Borland C++ však obsahuje několik dalších funkcí dynamického přidělování paměti. Při kompilaci kódu pro moderní 32bitový model paměti je paměť plochá a obvykle se používají pouze čtyři standardní funkce alokace paměti.

Standard ANSI C určuje, že informace záhlaví potřebné pro dynamické přidělování paměti jsou obsaženy v souboru stdlib.h. Borland C++ vám však umožňuje používat hlavičkové soubory stdlib.h nebo alloc.h. Zde používáme hlavičkový soubor stdlib.h, protože poskytuje přenositelnost. Některé další funkce dynamického přidělování paměti vyžadují soubory záhlaví alloc.h, malloc.h nebo dos.h. Věnujte zvláštní pozornost tomu, který soubor záhlaví je potřebný pro použití jednotlivých funkcí.

Objevili jsme možnosti dynamické alokace paměti. Co to znamená? To znamená, že při dynamické alokaci paměti není paměť rezervována ve fázi kompilace, ale ve fázi provádění programu. A to nám dává možnost efektivněji alokovat paměť, hlavně pro pole. Z dynamický výběr paměti, nemusíme přednastavovat velikost pole, zejména proto, že není vždy známo, jakou velikost má pole mít. Dále se podíváme na to, jak můžete alokovat paměť.

Alokace paměti v C (funkce malloc)

Funkce malloc() je definována v hlavičkový soubor stdlib.h , používá se k inicializaci ukazatelů s požadovaným množstvím paměti. Paměť je alokována ze sektoru paměť s náhodným přístupem dostupné všem programům spuštěným na tomto počítači. Argumentem je počet bajtů paměti, které mají být přiděleny, funkce vrací ukazatel na alokovaný blok v paměti. Funkce malloc() funguje stejně jako jakákoli jiná funkce, nic nového.

Protože odlišné typy data mají různé požadavky na paměť, musíme se nějak naučit, jak získat velikost v bajtech pro data jiný typ. Například potřebujeme část paměti pro pole hodnot typu int – jedná se o jednu velikost paměti, a pokud potřebujeme alokovat paměť pro pole stejné velikosti, ale již typu char – toto je jinou velikost. Proto je potřeba nějak spočítat velikost paměti. To lze provést pomocí operace sizeof(), která vezme výraz a vrátí jeho velikost. Například sizeof(int) vrátí počet bajtů potřebných k uložení hodnoty int. Zvažte příklad:

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

V tomto příkladu v řádek 3 ukazatel ptrVar má přiřazenu adresu části paměti, jejíž velikost odpovídá datovému typu int . Tato oblast paměti se automaticky stane nepřístupnou pro jiné programy. A to znamená, že poté, co se přidělená paměť stane nepotřebnou, musí být výslovně uvolněna. Pokud není paměť explicitně uvolněna, pak na konci programu nebude paměť uvolněna operační systém, nazývá se to únik paměti. Velikost alokované paměti, kterou je třeba alokovat, můžete také určit předáním nulového ukazatele, zde je příklad:

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

Co se tam děje? Operace sizeof(*ptrVar) odhadne velikost oblasti paměti, na kterou ukazuje ukazatel. Protože ptrVar je ukazatel na část paměti typu int , sizeof() vrátí velikost celého čísla. To znamená, že podle první části definice ukazatele se počítá velikost pro druhou část. Tak proč to potřebujeme? To může být nutné, pokud náhle potřebujete změnit definici ukazatele, například int, na float a pak nepotřebujeme měnit datový typ ve dvou částech definice ukazatele. Bude stačit, že změníme první část:

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

Jak vidíte, v takovém záznamu je jeden velmi silná stránka, neměli bychom volat funkci malloc() pomocí sizeof(float) . Místo toho jsme předali ukazatel na typ float malloc() , v takovém případě bude velikost přidělené paměti automaticky určena sama!

To je zvláště užitečné, pokud potřebujete alokovat paměť daleko od definice ukazatele:

Float *ptrVar; /* . . . sto řádků kódu */ . . . ptrVar = malloc(sizeof(*ptrVar));

Pokud byste použili konstrukci alokace paměti s operací sizeof(), pak byste museli v kódu najít definici ukazatele, podívat se na jeho datový typ a teprve potom byste byli schopni správně alokovat paměť.

Dealokace přidělené paměti

Uvolnění paměti se provádí pomocí funkce free(). Zde je příklad:

Free(ptrVar);

Po uvolnění paměti je dobrým zvykem vynulovat ukazatel na nulu, tj. nastavit *ptrVar = 0 . Pokud ukazateli přiřadíte 0, ukazatel se stane nulovým, jinými slovy, již nikam neukazuje. Vždy po uvolnění paměti přiřaďte ukazateli 0, jinak i po uvolnění paměti na něj ukazatel stále ukazuje, což znamená, že můžete náhodně poškodit jiné programy, které mohou tuto paměť používat, ale ani o tom nic nevíte. bude vědět a budete si myslet, že program funguje správně.

P.S.: Každý, kdo má rád střih videa, by se mohl zajímat o tento video editor pro Windows 7. Video editor se jmenuje Movavi, možná ho už někdo zná nebo s ním dokonce pracoval. S tímto programem v ruštině můžete snadno přidat video z kamery, zlepšit kvalitu a překrytí krásná videa efekty.

Statická paměť se přiděluje ještě před spuštěním programu, ve fázi kompilace a sestavování. Statické proměnné mají pevnou adresu, která je známá před spuštěním programu a během jeho činnosti se nemění. Statické proměnné jsou vytvořeny a inicializovány před vstupem do hlavní funkce ve kterém začíná provádění programu.

Existují dva typy statických proměnných:

  • globální proměnné jsou definovány proměnné vnější funkce, v jehož popisu chybí slovo statický. Obvykle popisy globální proměnné, které obsahují slovo extern, se přesunou do hlavičkových souborů (h-souborů). Slovo extern znamená, že proměnná je deklarována, ale není v tomto bodě programu vytvořena. Definice globální proměnné, tzn. popisy bez slova extern jsou umístěny v implementačních souborech (c-files nebo cpp-files). Příklad: globální proměnná maxind je deklarována dvakrát:
    • v h-souboru s řádkem

      extern int maxind;

      tato deklarace hlásí přítomnost takové proměnné, ale nevytváří tuto proměnnou!
    • v souboru cpp s řádkem

      int maxind = 1000;

      tento popis vytváří proměnná maxind a přiřadí jí počáteční hodnotu 1000 . Všimněte si, že jazykový standard nevyžaduje povinné přiřazení počátečních hodnot globálním proměnným, ale přesto je vždy lepší to udělat, jinak bude proměnná obsahovat nepředvídatelnou hodnotu (smetí, jak říkají programátoři). Je dobrým stylem inicializovat všechny globální proměnné, když jsou definovány.
    Globální proměnné jsou tak pojmenovány, protože jsou dostupné kdekoli v programu ve všech jeho souborech. Proto musí být názvy globálních proměnných dostatečně dlouhé, aby se zabránilo náhodným názvům dvou různých proměnných. Například názvy x nebo n pro globální proměnnou nejsou vhodné;
  • statické proměnné jsou proměnné, jejichž popis obsahuje slovo static . Zpravidla se deklarují statické proměnné vnější funkce. Takové statické proměnné jsou v každém ohledu jako globální proměnné, až na jednu výjimku: rozsah statické proměnné je omezen na jediný soubor, v němž je definována – a navíc ji lze použít až poté, co byla deklarována, tzn. níže v textu. Z tohoto důvodu se deklarace statických proměnných obvykle umísťují na začátek souboru. Na rozdíl od globálních proměnných, statických proměnných nikdy nejsou popsány v h-souborech (externí a statické modifikátory jsou ve vzájemném konfliktu). Tip: Pokud chcete, aby byly dostupné pouze pro funkce popsané uvnitř, použijte statické proměnné stejný soubor. Pokud je to možné, v takových situacích nepoužívejte globální proměnné, vyhnete se tak konfliktům názvů při implementaci velkých projektů sestávajících ze stovek souborů.
    • Statická proměnná může být také deklarována uvnitř funkce, ačkoli to obvykle nikdo nedělá. Proměnná není umístěna na zásobníku, ale ve statické paměti, tzn. nelze jej použít v rekurzi a jeho hodnota je zachována mezi různými vstupy funkce. Rozsah takové proměnné je omezen tělem funkce, ve které je definována. Jinak je to podobné jako u statické nebo globální proměnné. všimněte si, že klíčové slovo static v C se používá pro dva různé účely:
      • jako označení typu paměti: proměnná je umístěna ve statické paměti, nikoli na zásobníku;
      • jako způsob, jak omezit rozsah proměnné v rámci jednoho souboru (v případě deklarace proměnné mimo funkci).
  • Slovo statický se také může objevit v záhlaví funkce. Používá se však pouze k omezení rozsahu názvu funkce na jeden soubor. Příklad:

    static int gcd(int x, int y); // Funkční prototyp. . . static int gcd(int x, int y) ( // Implementace...)

    Tip: použijte statický modifikátor v hlavičce funkce, pokud je známo, že funkce bude volána pouze v rámci jednoho souboru. Slovo static musí být přítomno jak v popisu prototypu funkce, tak v hlavičce funkce, když je implementována.

Zásobník nebo místní paměť

Lokální nebo zásobníkové proměnné jsou deklarované proměnné uvnitř funkce. Paměť pro takové proměnné je alokována v hardwarovém zásobníku, viz část 2.3.2. Paměť je přidělena, když je zadána funkce nebo blok, a je uvolněna, když je funkce nebo blok opuštěn. V tomto případě dochází k zachycení a uvolnění paměti téměř okamžitě, protože. počítač pouze upravuje registr obsahující adresu vrcholu zásobníku.

Lokální proměnné lze použít v rekurzi, protože při opětovném zadání funkce se v zásobníku vytvoří nová sada lokálních proměnných a předchozí sada se nezničí. Ze stejného důvodu jsou lokální proměnné bezpečné při použití vláken v paralelním programování (viz část 2.6.2). Programátoři volají tuto vlastnost funkce opětovného vstupu, z angličtiny. re-enter schopný - schopnost znovu vstoupit. To je velmi důležitá kvalita z hlediska spolehlivosti a bezpečnosti programu! Program, který pracuje se statickými proměnnými, tuto vlastnost nemá, takže pro ochranu statických proměnných musíte použít synchronizační mechanismy(viz 2.6.2) a logika programu se stává mnohem složitější. Vždy byste se měli vyhnout používání globálních a statických proměnných, pokud si vystačíte s lokálními.

Nevýhody lokálních proměnných jsou rozšířením jejich výhod. Lokální proměnné se vytvářejí při vstupu funkce a mizí při jejím ukončení, takže je nelze použít jako data sdílená mezi více funkcemi. Velikost hardwarového zásobníku navíc není nekonečná, zásobník se může v jednom jemném okamžiku (například při hluboké rekurzi) přetéct, což povede ke katastrofálnímu ukončení programu. Lokální proměnné by proto neměly být velké. Zejména velká pole nelze použít jako lokální proměnné.

Dynamická paměť nebo halda

Kromě statické a zásobníkové paměti existuje také téměř neomezený zdroj paměti, který se nazývá dynamický nebo halda(hromada). Program dokáže zachytit oblasti dynamické paměti požadované velikosti. Po použití by měla být uvolněna dříve zachycená část dynamické paměti.

Prostor přidělený pro dynamickou paměť virtuální paměť proces mezi statickou pamětí a zásobníkem. (Mechanismus virtuální paměti byl diskutován v části 2.6.) Zásobník je obvykle umístěn na vyšších adresách virtuální paměti a roste ve směru klesajících adres (viz část 2.3). Program a konstantní data jsou umístěny na nižších adresách, statické proměnné jsou umístěny výše. Prostor nad statickými proměnnými a pod zásobníkem zabírá dynamická paměť:

adresa obsah paměti

programový kód a data,

chráněna před změnou

...

statické proměnné

programy

dynamická paměť

max. adresa (2 32 -4)

zásobník

Dynamická struktura paměti je automaticky udržována runtime systémem C nebo C++. Halda se skládá ze zachycených a volných segmentů, každému předchází deskriptor segmentu. Při provádění požadavku na uchopení paměti provádějící systém hledá volný segment dostatečné velikosti a uchopuje v něm segment požadované délky. Když je segment paměti uvolněn, je označen jako volný, v případě potřeby je spojeno několik po sobě jdoucích volných segmentů.

Jazyk C používá k získání a uvolnění dynamické paměti standardní funkce malloc a free, jejich prototypy jsou popsány ve standardním hlavičkovém souboru „stdlib.h“. (Jméno malloc je zkratka pro alokace paměti- "zachytávání paměti".) Prototypy těchto funkcí vypadají takto:

void *malloc(velikost_t n); // Uchopte část paměti // o velikosti n bajtů void free(void *p); // Uvolněte oblast // paměti s adresou p

Zde n je velikost zachycené oblasti v bajtech, size_t je název jednoho z celočíselných typů, které definují maximální velikost zachycená oblast. Typ size_t je specifikován ve standardním hlavičkovém souboru " stdlib.h " pomocí příkazu typedef (viz str. 117). Tím je zajištěno, že text programu C je nezávislý na použité architektuře. Na 32bitové architektuře je size_t definováno jako celé číslo bez znaménka:

typedef unsigned int size_t;

Funkce malloc vrací adresu zachycené oblasti paměti nebo nulu při selhání (pokud není dostatečně velká volná oblast). Volná funkce uvolní část paměti na dané adrese. K nastavení adresy se používá ukazatel. obecný typ neplatný*. Po zavolání funkce malloc je nutné ji přetypovat na ukazatel na konkrétní typ pomocí operace přetypování, viz Oddíl 3.4.11. Například následující příklad uchopí 4000bajtovou haldu a přiřadí její adresu ukazateli na pole 1000 celých čísel:

int*a; // Ukazatel na pole celých čísel. . . a = (int *) malloc(1000 * sizeof(int));

Výraz v argumentu funkce malloc je 4000, protože velikost celého čísla sizeof(int) je čtyři bajty. Chcete-li převést ukazatel, použijte operaci přetypování (int *) z ukazatele obecného typu na ukazatel na celé číslo.

Příklad: tisk prvních n prvočísel

Podívejme se na příklad, který využívá dynamické zachycování paměti. Musíte zadat celé číslo n a vytisknout prvních n prvočísel. (Prvočíslo je číslo, které nemá žádné netriviální dělitele.) Použijeme následující algoritmus: postupně kontrolujeme všechna lichá čísla počínaje trojkou (dvě uvažujeme samostatně). Další číslo vydělíme všemi prvočísly nalezenými v předchozích krocích algoritmu a nepřesahujícími druhou odmocninu kontrolovaného čísla. Pokud není dělitelné žádným z těchto prvočísel, pak je samo prvočíslo; vytiskne se a přidá do pole nalezených prvočísel.

Protože požadovaný počet prvočísel n není před spuštěním programu znám, není možné vytvořit pole pro jejich uložení do statické paměti. Východiskem je urvat místo pro pole v dynamické paměti již po zadání čísla n . Zde je plné znění programu:

#zahrnout #zahrnout #zahrnout int main() ( int n; // Požadovaný počet prvočísel int k; // Aktuální počet nalezených prvočísel int *a; // Ukazatel na pole nalezených prvočísel int p; // Další číslo pro kontrolu int r; // Část celého čísla druhá odmocnina z p int i; // Index prvočíselného dělitele bool prvočíslo; // Prvočíslo printf("Zadejte počet prvočísel: "); scanf("%d", &n); if (n<= 0) // Некорректное значение =>návrat 1; // ukončení s chybovým kódem // Zachycení paměti pro pole prvočísel a = (int *) malloc(n * sizeof(int)); a = 2; k = 1; // Přidá 2 do pole printf("%d ", a); // a vytiskni to p = 3; zatímco (k< 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 není prvočíslo, zlom; // ukončení cyklu ) ++i; // Na další prvočíslo ) if (prvočíslo) ( // Pokud najdete prvočíslo, a[k] = p; // pak ho přidejte do pole ++k; // Zvyšte počet prvočísel printf( "%d", p ); // Tisk prvočísla if (k % 5 == 0) ( // Přepnout na nový řádek printf("\n"); // po každých pěti číslech ) ) p += 2; // Na další liché číslo ) if (k % 5 != 0) ( printf("\n"); // Přeložit řetězec ) // Uvolnit dynamickou paměť free(a); návrat 0; )

Příklad, jak tento program funguje:

Zadejte počet jednoduchých: 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 197 197 197 197 197 197 197 197 197 197 197 197 197 197 199 211 223 227 229

C++ nové a odstraněné operátory

Jazyk C++ používá operátory new a delete k získání a uvolnění dynamické paměti. Jsou součástí jazyka C++, na rozdíl od funkcí malloc a free, které jsou součástí standardní knihovny C.

Nechť T je nějaký typ C nebo C++, p je ukazatel na objekt typu T . Poté se pro zachycení paměti o velikosti jednoho prvku typu T použije operátor new:

T*p; p = nové T;

Například zachytit osm bajtů pod reálné číslo fragment se používá typu double

double *p; p = nový dvojitý;

Při použití new , na rozdíl od malloc , nemusíte přetypovat ukazatel z void* na požadovaný typ: operátor new vrátí ukazatel na typ napsaný za slovem new . Porovnejte dva ekvivalentní úryvky v C a C++.

    ukládá globální proměnné a konstanty;

    velikost je určena v době kompilace.

    zásobník

    ukládá lokální proměnné, argumenty funkcí a mezilehlé hodnoty výpočtů;

    velikost je určena při spuštění programu (obvykle jsou přiděleny 4 MB).

    Halda

    dynamicky alokovaná paměť;

    Operační systém přiděluje paměť po částech (podle potřeby).

Dynamicky alokovaná paměť by měla být použita v případě, že předem (v době psaní programu) nevíme, kolik paměti budeme potřebovat (např. velikost pole závisí na tom, co uživatel za běhu programu zadá) a při práci s velkým množstvím dat.

Dynamická paměť, nazývaná také „hromada“, je explicitně alokována na žádost programu ze zdrojů operačního systému a je řízena ukazatelem. Není automaticky inicializován a musí být explicitně uvolněn. Na rozdíl od statické a automatické paměti je dynamická paměť prakticky neomezená (omezená pouze velikostí RAM) a může se měnit za běhu programu.

Práce s dynamickou pamětí v s

Pro práci s dynamickou pamětí v jazyce C se používají následující funkce: malloc, calloc, zdarma, realloc. Zvažme je podrobněji.

    Alokace (uchopení paměti) : void *malloc(velikost_t velikost);

Tak jako vstupní parametr funkce převezme velikost paměti, která má být přidělena. Vrácená hodnota je ukazatel na blok paměti alokovaný na haldě. Pokud OS nebyl schopen alokovat paměť (například nebyl dostatek paměti), malloc vrátí 0.

    Po dokončení práce s dynamicky alokovanou pamětí je třeba ji uvolnit. K tomuto účelu se používá funkce zdarma, který vrací paměť pod kontrolu OS: void free(void *ptr);

Pokud se dynamická paměť neuvolní před koncem programu, uvolní se automaticky po skončení programu. Je však známkou dobrého stylu programování explicitně uvolnit nepotřebnou paměť.

Příklad:// alokace paměti pro 1 000 int prvků

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

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

zdarma(p); // vrátí paměť do haldy

2. Alokace (zachycení paměti): void *calloc(size_t nmemb, size_t size);

Funkce funguje podobně jako malloc, liší se však syntaxí (místo velikosti alokované paměti je potřeba zadat počet prvků a velikost jednoho prvku) a tím, že alokovaná paměť bude vynulována. Například po provedení int * p = (int *) calloc(1000, sizeof(int)) p bude ukazovat na začátek pole int 1000 prvků inicializovaných na nulu.

3. Změna velikosti paměti: void *realloc(void *ptr, velikost_t velikost);

Funkce změní velikost přidělené paměti (na kterou ukazuje ptr, odvozené z hovoru malloc, calloc nebo realloc). Pokud je velikost uvedená v parametru velikost větší než přidělené pod ukazatelem ptr, poté se zkontroluje, zda je možné alokovat chybějící paměťové buňky v řadě s již přidělenými. Pokud není dostatek místa, je přidělena nová část paměti s velikostí velikost a data ukazatele ptr se zkopírují na začátek nové sekce.

Během provádění programu je k dispozici sekce dynamické paměti, kdykoli je k dispozici ukazatel adresující tuto sekci. Jsou tedy možné následující tři varianty práce s dynamickou pamětí alokovanou v nějakém bloku (například v těle nehlavní funkce).

    Ukazatel (na oblast dynamické paměti) je definován jako objekt místní automatické paměti. V tomto případě nebude přidělená paměť k dispozici při opuštění bloku lokalizace ukazatele a musí být před opuštěním bloku uvolněna.

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

zdarma(p); // zdarma dyn. Paměť

    Ukazatel je definován jako objekt lokálního statického úložiště. Dynamická paměť alokovaná jednou v bloku je dostupná prostřednictvím ukazatele při každém opětovném vstupu do bloku. Paměť by měla být uvolněna pouze tehdy, když se již nepoužívá.

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

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

f(50); //zvýraznit din. paměť, která se má uvolnit

fl(100); //zvýraznit din. paměť (první přístup)

fl(100); // práce s din. Paměť

fl(0); // zdarma dyn. Paměť

    Ukazatel je globální objekt vzhledem k bloku. Dynamická paměť je dostupná ve všech blocích, kde je ukazatel „viditelný“. Paměť by měla být uvolněna pouze tehdy, když se již nepoužívá.

int*pG; //funkční ukazatel pro din. paměť (globální proměnná)

void init (int size)

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

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

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

int součet (int size)

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

// alokace paměti

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

// práce s dynamickou pamětí

printf(\ns=%d\n",sum(n));

zdarma (pG); pG=NULL; // uvolnit paměť

Práce s dynamickou pamětí v C++

C++ má svůj vlastní mechanismus pro alokaci a uvolnění paměti – to jsou funkce Nový a vymazat. Příklad použití Nový: int * p = nový int; // přidělení paměti za 1000 e-tis, tj. při použití funkce Nový není třeba vrhat ukazatel a není třeba používat velikost(). Uvolnění výběru pomocí Nový paměť je zpracována následujícím voláním: delete p; Pokud potřebujete alokovat paměť pro jeden prvek, můžete použít int * q = new int; nebo int * q = new int(10); // přidělený int bude inicializován hodnotou 10 v tomto případě bude smazání vypadat takto: delete q;

C++ podporuje tři základní typy přidělení(nebo více "distribuce") Paměť, z nichž dva již známe:

Alokace statické paměti platí pro a proměnné. Paměť je přidělena jednou při spuštění programu a je zachována v průběhu celého programu.

Automatické přidělování paměti se provádí pro a . Paměť je přidělena, když je blok obsahující tyto proměnné vložen, a odebrána, když je opuštěn.

Dynamická alokace paměti je tématem této lekce.

Dynamická alokace proměnných

Statické i automatické přidělování paměti mají dvě společné vlastnosti:

Jak funguje dynamické přidělování paměti?

Váš počítač má paměť (možná hodně), která je k dispozici pro použití programy. Když spustíte program, váš operační systém nahraje tento program do nějaké části této paměti. A tato paměť používaná vaším programem je rozdělena do několika částí, z nichž každá plní specifický úkol. Jedna část obsahuje váš kód, druhá slouží k provádění běžných operací (sledování volaných funkcí, vytváření a ničení globálních a lokálních proměnných atd.). Promluvíme si o tom později. Většina dostupné paměti tam však prostě čeká na žádosti o přidělení od programů.

Když dynamicky přidělujete paměť, žádáte operační systém, aby vyhradil část této paměti pro použití vašeho programu. Pokud OS může splnit tento požadavek, pak se adresa této paměti vrátí zpět do vašeho programu. Od této chvíle bude váš program moci používat tuto paměť, jak si přeje. Když jste s touto pamětí již udělali vše, co bylo nutné, je třeba ji vrátit zpět do operačního systému pro distribuci mezi další požadavky.

Na rozdíl od statické nebo automatické alokace paměti je za vyžádání a vrácení dynamicky přidělené paměti zodpovědný samotný program.

Uvolnění paměti

Když dynamicky alokujete proměnnou, můžete ji také inicializovat s nebo jednotnou inicializací (v C++11):

int *ptr1 = new int(7); // použití přímé inicializace int *ptr2 = new int ( 8 ); // použijte jednotnou inicializaci

Když už bylo vše potřebné udělat s dynamicky alokovanou proměnnou, musíte C++ explicitně říct, aby tuto paměť uvolnilo. U proměnných se to provádí pomocí operátor vymazat:

// Předpokládejme, že ptr již byl přidělen novému delete ptr; // vrátí paměť, na kterou ukazuje ptr, zpět operačnímu systému ptr = 0; // make ptr null (použijte nullptr místo 0 v C++11)

Operátor delete ve skutečnosti nic nevymaže. Jednoduše vrátí paměť, která byla dříve přidělena, zpět operačnímu systému. Operační systém pak může tuto paměť přiřadit jiné aplikaci (nebo znovu stejné).

I když se může zdát, že odstraňujeme variabilní ale není! Proměnná ukazatele má stále stejný rozsah jako dříve a lze jí přiřadit novou hodnotu stejně jako jakékoli jiné proměnné.

Všimněte si, že odstranění ukazatele, který neukazuje na dynamicky alokovanou paměť, může vést k problémům.

závěsné ukazatele

C++ neposkytuje žádné záruky ohledně toho, co se stane s obsahem uvolněné paměti nebo s hodnotou ukazatele, který je odstraňován. Ve většině případů bude paměť vrácená operačnímu systému obsahovat stejné hodnoty, jaké měla dříve uvolnění a ukazatel bude nadále ukazovat pouze na již uvolněnou (smazanou) paměť.

Zavolá se ukazatel, který ukazuje na uvolněnou paměť závěsný ukazatel. Dereferencování nebo odstranění visícího ukazatele povede k neočekávaným výsledkům. Zvažte následující program:

#zahrnout int main() ( int *ptr = new int; *ptr = 8; // vloží hodnotu do přiděleného paměťového místa delete ptr; // vrátí paměť zpět do operačního systému. ptr je nyní visící ukazatel std:: cout<< *ptr; // разыменование висячего указателя приведёт к неожиданным результатам delete ptr; // попытка освободить память снова приведёт к неожиданным результатам также return 0; }

#zahrnout

int main()

int * ptr = new int ; // dynamicky alokuje celočíselnou proměnnou

* ptr = 8; // vložte hodnotu do přiděleného paměťového místa

odstranit ptr; // vrátí paměť zpět do operačního systému. ptr je nyní visící ukazatel

std::cout<< * ptr ; // dereferencování visícího ukazatele povede k neočekávaným výsledkům

odstranit ptr; // pokus o opětovné uvolnění paměti povede také k neočekávaným výsledkům

návrat 0;

Ve výše uvedeném programu hodnota 8, která byla dříve přiřazena dynamické proměnné, zde může nebo nemusí být i po jejím uvolnění. Je také možné, že uvolněná paměť již mohla být přidělena jiné aplikaci (nebo pro vlastní potřebu operačního systému) a pokus o přístup k ní způsobí, že operační systém automaticky ukončí váš program.

Proces uvolňování paměti může také vést k vytvoření několik závěsné ukazatele. Zvažte následující příklad:

#zahrnout int main() ( int *ptr = new int; // dynamicky alokuje celočíselnou proměnnou int *otherPtr = ptr; // otherPtr nyní ukazuje na stejnou přidělenou paměť jako ptr delete ptr; // vrátí paměť zpět operačnímu systému ptr a otherPtr jsou nyní visící ukazatele ptr = 0; // ptr je nyní nullptr // OtherPtr je však stále visící ukazatel! return 0; )

#zahrnout

int main()

int * ptr = new int ; // dynamicky alokuje celočíselnou proměnnou

int * otherPtr = ptr ; // otherPtr nyní ukazuje na stejnou přidělenou paměť jako ptr

odstranit ptr; // vrátí paměť zpět do operačního systému. ptr a otherPtr jsou nyní visící ukazatele

ptr = 0; // ptr je nyní nullptr

// OtherPtr je však stále visící ukazatel!

návrat 0;

Nejprve se snažte vyhnout situacím, kdy více ukazatelů ukazuje na stejnou část přidělené paměti. Pokud to není možné, ujasněte si, který ukazatel ze všech „vlastní“ paměť (a je zodpovědný za její smazání) a které ukazatele k ní jednoduše přistupují.

Za druhé, když smažete ukazatel, a pokud se ihned po smazání neukončí, musí být nastaven na hodnotu null, tj. přiřadit hodnotu 0 (nebo v C++11). Tím "mimo rozsah ihned po smazání" mám na mysli, že smažete ukazatel na samém konci bloku, ve kterém je deklarován.

Pravidlo: Nastavte odstraněné ukazatele na 0 (nebo nullptr v C++11), pokud nepřejdou mimo rozsah okamžitě po odstranění.

nového operátora

Když operační systém požaduje paměť, ve vzácných případech nemusí být dostupná (tj. nemusí být dostupná).

Ve výchozím nastavení, pokud nový operátor nefungoval, paměť nebyla přidělena, pak a výjimka bad_alloc. Pokud tato výjimka není správně zpracována (což bude, protože jsme se ještě nedívali na výjimky a jejich zpracování), program se jednoduše přeruší (spadne) s neošetřenou chybou výjimky.

V mnoha případech je proces vyvolání výjimky s operátorem new (stejně jako zhroucení programu) nežádoucí, takže existuje alternativní forma operátoru new, která vrací nulový ukazatel, pokud nelze alokovat paměť. Stačí jen přidat std::nothrow konstanta mezi novým klíčovým slovem a datovým typem:

int *hodnota = new (std::nothrow) int; // hodnota ukazatele bude nulová, pokud selže dynamická alokace celočíselné proměnné

Pokud ve výše uvedeném příkladu new nevrátí ukazatel s dynamicky alokovanou pamětí, bude vrácen ukazatel null.

Dereferencování se také nedoporučuje, protože to povede k neočekávaným výsledkům (s největší pravděpodobností k selhání programu). Nejlepším postupem je proto zkontrolovat všechny požadavky na přidělení paměti, abyste se ujistili, že tyto požadavky jsou úspěšně provedeny a paměť je přidělena:

int *hodnota = new (std::nothrow) int; // požadavek na přidělení dynamické paměti pro celočíselnou hodnotu if (!value) // zpracování případu, kdy new vrátí null (tj. není přidělena žádná paměť) ( // Zpracování tohoto případu std::cout<< "Could not allocate memory"; }

Protože nepřidělení paměti novým operátorem je extrémně vzácné, programátoři na tuto kontrolu obvykle zapomínají!

Nulové ukazatele a dynamické přidělování paměti

Nulové ukazatele (ukazatele s hodnotou 0 nebo nullptr) jsou zvláště užitečné při dynamické alokaci paměti. Jejich přítomnost nám říká: "Tomuto ukazateli nebyla přidělena žádná paměť." A to lze zase použít k provedení podmíněné alokace paměti:

// Pokud ptr ještě nebyla přidělena paměť, pak ji alokujte if (!ptr) ptr = new int;

Odstranění nulového ukazatele nic neovlivní. Není tedy nutné následující:

if (ptr) delete ptr;

pokud (ptr)

odstranit ptr;

Místo toho můžete napsat:

odstranit ptr;

Pokud ptr není null, bude dynamicky alokovaná proměnná odstraněna. Pokud je hodnota ukazatele null, nic se nestane.

Únik paměti

Dynamicky alokovaná paměť nemá žádný rozsah, tzn. zůstává přidělena, dokud není explicitně uvolněna nebo dokud váš program neukončí své provádění (a operační systém sám nevyprázdní všechny vyrovnávací paměti). Ukazatele používané k ukládání adres dynamicky přidělované paměti se však řídí pravidly normálního rozsahu proměnných. Tento nesoulad může způsobit zajímavé chování. Například:

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