Dinamik yaddaşın ayrılması c. C dilinin nəzarət konstruksiyaları. Proqramların funksiyalar kimi təqdim edilməsi. Yaddaşla işləmək. Strukturlar. Standart dinamik yaddaş ayırma funksiyaları

Obyekt yönümlü inkişafa keçməzdən əvvəl C++ proqramında yaddaşla işləmək haqqında qısa məlumat verməliyik. İcra zamanı yaddaş ayıra bilmədən və ona daxil olmadan heç bir mürəkkəb proqramı yaza bilməyəcəyik.
C++-da obyektlər ya tərtib zamanı statik, ya da standart kitabxanadan funksiyaları çağırmaqla icra zamanı dinamik olaraq ayrıla bilər. Bu metodlardan istifadənin əsas fərqi onların səmərəliliyi və çevikliyidir. Statik yerləşdirmə daha səmərəlidir, çünki yaddaşın ayrılması proqram icra olunmazdan əvvəl baş verir, lakin o, daha az çevikdir, çünki biz əvvəlcədən ayrılan obyektin növünü və ölçüsünü bilməliyik. Məsələn, mətn faylının məzmununu statik sətirlər massivində yerləşdirmək heç də asan deyil: onun ölçüsünü əvvəlcədən bilməliyik. Naməlum sayda elementlərin saxlanmasını və işlənməsini tələb edən tapşırıqlar adətən dinamik yaddaşın ayrılmasını tələb edir.
İndiyə qədər bütün nümunələrimiz statik yaddaş ayrılmasından istifadə etmişdir. Deyək ki, ival dəyişənini təyin edək

Int ival = 1024;

kompilyatoru yaddaşda int tipli dəyişəni saxlamaq üçün kifayət qədər böyük sahə ayırmağa, ival adını bu sahə ilə əlaqələndirməyə və orada 1024 dəyərini yerləşdirməyə məcbur edir.
İval obyekti ilə əlaqəli iki dəyər var: dəyişənin faktiki dəyəri, bu halda 1024 və bu dəyərin saxlandığı yaddaş sahəsinin ünvanı. Bu iki kəmiyyətdən birinə müraciət edə bilərik. Yazdığımız zaman:

Int ival2 = ival + 1;

sonra biz ival dəyişəninin tərkibində olan qiymətə daxil oluruq: ona 1 əlavə edirik və ival2 dəyişənini bu yeni qiymətlə, 1025 ilə inisiallaşdırırıq. Dəyişənin yerləşdiyi ünvana necə daxil ola bilərik?
C++ obyektlərin ünvanlarını saxlamaq üçün istifadə edilən daxili göstərici tipinə malikdir. ival dəyişəninin ünvanını ehtiva edən göstəricini elan etmək üçün yazmalıyıq:

Int *pint; // int tipli obyektə göstərici

Həm də & simvolu ilə qeyd olunan ünvanı almaq üçün xüsusi əməliyyat var. Onun nəticəsi obyektin ünvanıdır. Aşağıdakı ifadə pint göstəricisinə ival dəyişəninin ünvanını təyin edir:

Int *pint; pint = // pint ival ünvanın dəyərini alır

Biz əməliyyatdan istifadə edərək ünvanı pint (bizim halda ival) olan obyektə daxil ola bilərik istinaddan imtina, həmçinin deyilir dolayı ünvanlama. Bu əməliyyat * işarəsi ilə göstərilir. Ünvanından istifadə edərək ival-a dolayı yolla birini necə əlavə etmək olar:

*pint = *pint + 1; // dolayısı ilə ival artırır

Bu ifadə tam olaraq eyni şeyi edir

Ival = ival + 1; // açıq şəkildə ival artırır

Bu misalın heç bir real mənası yoxdur: ival dəyişənini dolayı yolla manipulyasiya etmək üçün göstəricidən istifadə daha az səmərəli və daha az aydındır. Bu nümunəni yalnız göstəricilər haqqında çox əsas bir fikir vermək üçün verdik. Əslində, göstəricilər ən çox dinamik olaraq ayrılmış obyektləri manipulyasiya etmək üçün istifadə olunur.
Statik və dinamik yaddaş bölgüsü arasındakı əsas fərqlər bunlardır:

  • statik obyektlər adlandırılmış dəyişənlərlə təmsil olunur və bu obyektlər üzərində hərəkətlər birbaşa adlarından istifadə etməklə həyata keçirilir. Dinamik obyektlərin xüsusi adları yoxdur və onlar üzərində hərəkətlər dolayı yolla, göstəricilərdən istifadə etməklə həyata keçirilir;
  • Kompilyator avtomatik olaraq statik obyektlər üçün yaddaş ayırır və boşaldır. Proqramçının özünün bu barədə narahat olmasına ehtiyac yoxdur. Dinamik obyektlər üçün yaddaşın ayrılması və boşaldılması tamamilə proqramçının üzərinə düşür. Bu olduqca mürəkkəb bir işdir və onu həll edərkən səhv etmək asandır. new və delete operatorları dinamik olaraq ayrılmış yaddaşı manipulyasiya etmək üçün istifadə olunur.

Yeni operatorun iki forması var. Birinci forma yaddaşı müəyyən tipli bir obyekt üçün ayırır:

Int *pint = yeni int(1024);

Burada yeni operator int tipli adsız obyekt üçün yaddaş ayırır, onu 1024 qiyməti ilə inisiallaşdırır və yaradılmış obyektin ünvanını qaytarır. Bu ünvan pint göstəricisini işə salmaq üçün istifadə olunur. Belə bir adsız obyekt üzərində bütün hərəkətlər bu göstəriciyə istinad edərək yerinə yetirilir, çünki Dinamik obyekti açıq şəkildə manipulyasiya etmək mümkün deyil.
Yeni operatorun ikinci forması müəyyən tipli elementlərdən ibarət verilmiş ölçülü massiv üçün yaddaş ayırır:

Int *pia = yeni int;

Bu nümunədə yaddaş dörd int elementdən ibarət massiv üçün ayrılmışdır. Təəssüf ki, yeni operatorun bu forması massiv elementlərini işə salmağa imkan vermir.
Bəzi çaşqınlığa səbəb odur ki, yeni operatorun hər iki forması eyni göstəricini qaytarır, bizim nümunəmizdə o, tam ədədin göstəricisidir. Həm pint, həm də pia tam olaraq eyni elan edilir, lakin pint tək int obyektinə, pia isə dörd int obyektindən ibarət massivin birinci elementinə işarə edir.
Dinamik obyektə ehtiyac qalmadıqda, ona ayrılmış yaddaşı açıq şəkildə buraxmalıyıq. Bu, yeni kimi, iki formaya malik olan silmə operatorundan istifadə etməklə edilir - tək obyekt və massiv üçün:

// tək obyektin silinməsi pinti; // silsilənin boşaldılması pia;

Ayrılmış yaddaşı boşaltmağı unutsaq nə olar? Yaddaş boşa gedəcək, istifadə edilməyəcək, lakin sistemə qaytarıla bilməz, çünki ona bir göstəricimiz yoxdur. Bu fenomen xüsusi bir ad aldı yaddaş sızması. Nəhayət, proqram yaddaş çatışmazlığı səbəbindən çökəcək (əlbəttə ki, kifayət qədər uzun müddət işləyirsə). Kiçik bir sızıntı aşkar etmək çətin ola bilər, lakin bunu etməyə kömək edə biləcək kommunal proqramlar var.
Dinamik yaddaş bölgüsü və göstərici istifadəsinə dair qısa icmalımız yəqin ki, cavab verdiyindən daha çox sual doğurdu. Bölmə 8.4-də təfərrüatlı məsələlər əhatə olunacaq. Lakin sonrakı bölmələrdə dizayn edəcəyimiz Array sinfi dinamik olaraq ayrılmış yaddaşın istifadəsinə əsaslandığı üçün bu təxribat olmadan edə bilməzdik.

Məşq 2.3

Dörd obyekt arasındakı fərqi izah edin:

(a) int ival = 1024; (b) int *pi = (c) int *pi2 = yeni int(1024); (d) int *pi3 = yeni int;

Məşq 2.4

Aşağıdakı kod parçası nə edir? Məntiqi səhv nədir? (Qeyd edək ki, index() əməliyyatı pia göstəricisinə düzgün tətbiq edilib. Bu faktın izahını Bölmə 3.9.2-də tapa bilərsiniz.)

Int *pi = yeni int(10); int *pia = yeni int;
isə (*pi< 10) {
pia[*pi] = *pi; *pi = *pi + 1;
) pi silin; pia silmək;

Belə ki. Bu mövzuda bizim üçün ən maraqlı olan üçüncü növ yaddaşın dinamik növüdür.

Əvvəllər massivlərlə necə işləyirdik? int a İndi necə işləyirik? Lazım olan qədər ayırırıq:

#daxildir < stdio.h> #daxildir < stdlib.h> int main() (size_t ölçüsü; // int üçün göstərici yaradın // – mahiyyətcə boş massiv. int *siyahısı; scanf( "%lu", &ölçüsü); // int ölçüsünün elementləri üçün yaddaş ayırın // və bizim "boş massiv" indi bu yaddaşa istinad edir. siyahı = (int *) malloc (ölçü * sizeof (int)); üçün (int i = 0; i< size; ++i) { scanf (" %d " < size; ++i) { printf (" %d ", *(siyahı + i)); ) // Özünüzü təmizləməyi unutmayın! pulsuz (siyahı); ) // *

Void * malloc(size_t ölçüsü);

Ancaq ümumiyyətlə, bu funksiya işə salınmamış yaddaşın baytlarını (sıfırlar deyil, zibil) ayırır.

Ayrılma uğurlu olarsa, ayrılmış yaddaşın ilk baytına bir göstərici qaytarılır.

Əgər uğursuz olarsa - NULL. Həmçinin errno ENOMEM-ə bərabər olacaq (bu gözəl dəyişənə daha sonra baxacağıq). Yəni yazmaq daha düzgün olardı:

#daxildir < stdio.h> #daxildir < stdlib.h> int main () ( size_t ölçüsü; int * siyahı; scanf ( "%lu", &ölçüsü); siyahı = (int *) malloc (ölçü * sizeof (int)); if (list == NULL ) ( keçid xətası; ) üçün (int i = 0 ; i)< size; ++i) { scanf (" %d ", siyahı + i); ) üçün (int i = 0 ; i< size; ++i) { printf (" %d ", *(siyahı + i)); ) pulsuz (siyahı); 0 qaytarın; səhv: 1 qaytarın; ) // *

NULL göstəricini silməyə ehtiyac yoxdur

#daxildir < stdlib.h> int main() (pulsuz (NULL);)

- eyni cingiltidə hər şey yaxşı gedəcək (heç bir şey edilməyəcək), lakin daha ekzotik hallarda bu proqramı poza bilər.

Malloc və manada pulsuz yanında siz də görə bilərsiniz:

    void * calloc(ölçü_t sayı, ölçü_t ölçüsü);

    Malloc da bayt ölçülü obyektlərin sayı üçün yaddaş ayıracağı kimi. Ayrılmış yaddaş sıfırlarla işə salınır.

    void * realloc (void *ptr, size_t ölçüsü);

    Ptr ilə göstərilən yaddaşı bayt ölçüsünə görə yenidən yerləşdirir (əgər bacararsa). ptr tərəfindən göstərilən ayrılmış yaddaşı artırmaq üçün kifayət qədər yer yoxdursa, realloc yeni ayırma (ayırma) yaradır, ptr tərəfindən göstərilən köhnə məlumatları kopyalayır, köhnə ayırmanı azad edir və ayrılmış yaddaşa göstərici qaytarır.

    Əgər ptr NULL olarsa, realloc malloc çağırışı ilə eynidir.

    Ölçü sıfırdırsa və ptr NULL deyilsə, minimum ölçülü yaddaş parçası ayrılır və orijinalı boşaldılır.

    void * reallocf (void *ptr, size_t ölçüsü);

    FreeBSD API-dən bir fikir. realloc kimi, lakin yenidən bölüşdürə bilmirsə, qəbul edilmiş göstəricini təmizləyir.

    void * valloc (size_t ölçüsü);

    Malloc kimi, lakin ayrılmış yaddaş səhifə hizalanır.

Bir çox başqa dillərdə olduğu kimi, C++ dilində də yaddaş statik (proqramın icrası başlamazdan əvvəl ayrılır və proqram başa çatdıqdan sonra boşaldılır) və ya dinamik (proqramın icrası zamanı yaddaş ayrılır və boşaldılır) bölünə bilər.

Statik yaddaşın ayrılması proqramda açıq bəyannamələri olan bütün qlobal və yerli dəyişənlər üçün həyata keçirilir (göstəricilərdən istifadə etmədən). Bu halda yaddaşın bölüşdürülməsi mexanizmi proqramda dəyişənin təsvirinin yeri və təsvirdə yaddaş sinfi təyinedicisi ilə müəyyən edilir. Dəyişənin növü ayrılmış yaddaş sahəsinin ölçüsünü müəyyən edir, lakin yaddaşın bölüşdürülməsi mexanizmi tipdən asılı deyil. Statik yaddaşın ayrılması üçün iki əsas mexanizm var.

· Qlobal və statik (statik spesifikatorla elan edilmiş) dəyişənlərin hər biri üçün yaddaş növün təsvirinə uyğun olaraq proqramın icrasına başlamazdan əvvəl ayrılır. Proqramın icrasının əvvəlindən sonuna kimi bu dəyişənlər onlar üçün ayrılmış yaddaş sahəsi ilə əlaqələndirilir. Beləliklə, onların qlobal ömürləri var, lakin onların görünmə dairəsi fərqlidir.

· Blok daxilində elan edilmiş və spesifikatorsuz lokal dəyişənlər üçün statik yaddaş fərqli bir şəkildə ayrılır. Proqramın icrasına başlamazdan əvvəl (yükləndikdə) kifayət qədər böyük yaddaş sahəsi ayrılır. yığın(bəzən terminlərdən istifadə olunur proqram yığını və ya zəng yığını mücərrəd məlumat növü kimi yığın arasında fərq qoymaq üçün). Yığın ölçüsü inkişaf mühitindən asılıdır, məsələn, MS Visual C++-da, standart olaraq stek üçün 1 meqabayt ayrılmışdır (bu dəyər fərdiləşdirilə bilər). Proqramın icrası zamanı müəyyən bloka daxil olan zaman blokda lokallaşdırılmış dəyişənlər üçün yaddaş ayrılır (blokdan çıxdıqda onların tipinin təsvirinə uyğun olaraq bu yaddaş boşaldılır); Bu proseslər avtomatik olaraq yerinə yetirilir, buna görə də C++ dilində lokal dəyişənlər tez-tez çağırılır avtomatik.

Funksiya çağırıldıqda, onun lokal dəyişənləri, parametrləri (parametrin dəyəri və ya ünvanı stekdə yerləşdirilir), funksiyanın nəticəsi və qayıtma nöqtəsinin saxlanması üçün stekdə yaddaş ayrılır - proqramdakı ünvan. funksiya tamamlandıqda geri qayıtmalısınız. Funksiya çıxdıqda, onunla əlaqəli bütün məlumatlar yığından silinir.

"Yığın" termininin istifadəsini izah etmək asandır - yaddaşın ayrılması və boşaldılması üçün qəbul edilmiş yanaşma ilə stekdə sonuncu yerləşdirilmiş dəyişənlər (bunlar ən dərin yuvalanmış blokda lokallaşdırılmış dəyişənlərdir) ondan ilk olaraq çıxarılır. Yəni yaddaşın ayrılması və buraxılması LIFO prinsipinə uyğun olaraq baş verir (LAST IN – FIRST OUT, last in – first out). Bu, yığının necə işləmə prinsipidir. Biz növbəti hissədə stekə dinamik məlumat strukturu və onun mümkün tətbiqi kimi baxacağıq.



Bir çox hallarda statik olaraq ayrılmış yaddaş ondan səmərəsiz istifadəyə gətirib çıxarır (bu, xüsusilə böyük massivlər üçün doğrudur), çünki statik olaraq ayrılmış yaddaş sahəsi həmişə faktiki olaraq verilənlərlə doldurulmur. Buna görə də, bir çox dillərdə olduğu kimi, C++ dilində də dəyişənləri dinamik şəkildə yaratmaq üçün əlverişli vasitələr mövcuddur. Dinamik yaddaşın bölüşdürülməsinin mahiyyəti ondan ibarətdir ki, yaddaş proqramın tələbi ilə ayrılır (tutulur), həm də sorğu əsasında boşaldılır. Bu halda, yaddaş ölçüsü dəyişənin növü ilə müəyyən edilə bilər və ya sorğuda açıq şəkildə göstərilə bilər. Belə dəyişənlər deyilir dinamik. Dinamik dəyişənlər yaratmaq və istifadə etmək bacarığı göstərici mexanizmi ilə sıx bağlıdır.

Yuxarıda deyilənlərin hamısını ümumiləşdirərək proqramın icrası zamanı yaddaşın aşağıdakı yerləşdirilməsi sxemini təsəvvür edə bilərik (Şəkil 2.1). Şəkildə sahələrin bir-birinə nisbətən yeri olduqca ixtiyaridir, çünki Əməliyyat sistemi yaddaşın ayrılması detallarına diqqət yetirir.

Şəkil 2.1 – yaddaş paylama diaqramı

Bu bölməni yekunlaşdırmaq üçün yığınla işləyərkən bir ağrılı problemə - onun daşması ehtimalına toxunaq (bu fövqəladə vəziyyət adətən adlanır. Stack Overflow). Problemə səbəb olan səbəb aydındır - proqramı yükləyərkən yığın üçün ayrılan məhdud yaddaş miqdarı. Yığın daşması üçün ən çox ehtimal olunan vəziyyətlər böyük yerli massivlər və rekursiv funksiya çağırışlarının dərin yuvalanmasıdır (adətən rekursiv funksiyaların proqramlaşdırılması qeyri-dəqiq olduqda baş verir, məsələn, bəzi terminal filialı unudulub).



Yığın daşması problemini daha yaxşı başa düşmək üçün bu sadə təcrübəni yerinə yetirməyi tövsiyə edirik. Funksiyada əsasölçüsündə demək olar ki, bir milyon elementi olan tam ədədlər massivini elan edin. Proqram tərtib ediləcək, lakin siz onu işə saldığınız zaman yığının daşması xətası baş verəcək. İndi spesifikatoru massiv təsvirinin əvvəlinə əlavə edin statik(və ya massiv elanını funksiyadan çıxarın əsas) – proqram işləyəcək!

Bunda möcüzəli bir şey yoxdur - sadəcə olaraq indi massiv yığına deyil, qlobal və statik dəyişənlər sahəsinə yerləşdirilib. Bu sahə üçün yaddaş ölçüsü kompilyator tərəfindən müəyyən edilir - proqram tərtib edilibsə, o zaman işləyəcək.

Bununla belə, bir qayda olaraq, proqramda nəhəng ölçülü statik olaraq yaradılmış massivləri elan etməyə ehtiyac yoxdur. Əksər hallarda bu cür məlumatlar üçün yaddaşın dinamik şəkildə ayrılması daha səmərəli və çevik üsul olacaqdır.

Dinamik və statik yaddaşın ayrılması. Yaxşı və pis tərəfləri. Yeni və sil operatorlarından istifadə edərək tək dəyişənlər üçün yaddaşın ayrılması. Yaddaşın ayrılması zamanı mümkün kritik vəziyyətlər. Yaddaş ayrıldıqda başlanğıc

1. Dinamik və statik (sabit) yaddaşın ayrılması. Əsas fərqlər

İnformasiya massivləri ilə işləmək üçün proqramlar bu massivlər üçün yaddaş ayırmalıdır. Dəyişən massivlər üçün yaddaş ayırmaq üçün C++ proqramlaşdırma dilində müvafiq operatorlar, funksiyalar və s. istifadə olunur.

1. Statik (sabit) yaddaşın ayrılması. Bu halda yaddaş kompilyasiya zamanı yalnız bir dəfə ayrılır. Ayrılmış yaddaşın ölçüsü sabitdir və proqramın icrasının sonuna qədər dəyişməz qalır. Belə seçimə misal olaraq 10 tam ədəddən ibarət massiv elan etmək olar:

int M; // massiv üçün yaddaş bir dəfə ayrılır, yaddaşın ölçüsü müəyyən edilir

2. Dinamik yaddaşın ayrılması. Bu zaman new və delete operatorlarının kombinasiyasından istifadə edilir. Yeni operator yaddaşı yığın adlanan xüsusi yaddaş sahəsində dəyişən (massiv) üçün ayırır. Silmə operatoru ayrılmış yaddaşı boşaldır. Hər yeni operatorun öz silmə operatoru olmalıdır.

2. Dinamik və statik yaddaş ayırma üsullarından istifadənin üstünlükləri və çatışmazlıqları

Dinamik yaddaş bölgüsü statik yaddaş bölgüsü ilə müqayisədə aşağıdakı üstünlükləri təmin edir:

  • yaddaş proqramla lazım olduqda ayrılır;
  • İstifadə edilməmiş yaddaşın lazımsız israfı yoxdur. Lazım olduğu qədər yaddaş ayrılır və lazım olduqda;
  • ölçüsü açıq-aydın bilinməyən informasiya massivləri üçün yaddaş ayırmaq mümkündür. Massivin ölçüsü proqramın icrası zamanı müəyyən edilir;
  • Yaddaşın yenidən paylanması rahatdır. Və ya başqa sözlə, əlavə yaddaş ayırmaq və ya lazımsız yaddaşı boşaltmaq lazımdırsa, eyni massiv üçün yeni fraqment ayırmaq rahatdır;
  • Statik yaddaş ayırma metodu ilə massiv dəyişəni üçün yaddaşı yenidən bölüşdürmək çətindir, çünki o, artıq sabit şəkildə ayrılmışdır. Dinamik seçim metodu vəziyyətində bu, sadə və rahat şəkildə həyata keçirilir.

Statik yaddaş ayırma metodunun üstünlükləri:

  • statik (sabit) yaddaşın ayrılması ən yaxşı məlumat massivinin ölçüsü məlum olduqda və bütün proqramın icrası ərzində dəyişməz qaldıqda istifadə olunur;
  • statik yaddaşın ayrılması sil operatorundan istifadə edərək əlavə boşalma əməliyyatlarını tələb etmir. Bu proqramlaşdırma səhvlərinin azalması ilə nəticələnir. Hər bir yeni operatorun öz silmə operatoru olmalıdır;
  • statik massivlərlə işləyən proqram kodunun təqdimatının təbiiliyi (təbiiliyi).

Qarşıya qoyulan vəzifədən asılı olaraq proqramçı müəyyən dəyişən (massiv) üçün hansı yaddaşın ayrılması metodunun uyğun olduğunu düzgün müəyyən etməyi bacarmalıdır.

3. Tək dəyişən üçün new operatorundan istifadə edərək yaddaşı necə ayırmaq olar? Ümumi forma.

Yeni operatordan istifadə edərək tək dəyişən üçün yaddaşın ayrılmasının ümumi forması aşağıdakı kimidir:

ptrName= yeni növ;
  • ptrName– ayrılmış yaddaşa işarə edəcək dəyişənin (göstəricinin) adı;
  • növü- dəyişən növü. Yaddaş ölçüsü bu tip dəyişənin dəyərini orada yerləşdirmək üçün kifayət qədər ayrılmışdır. növü .
4. Sil operatorundan istifadə etməklə tək dəyişən üçün ayrılmış yaddaşı necə boşaltmaq olar? Ümumi forma

Əgər dəyişən üçün yaddaş new operatoru vasitəsilə ayrılırsa, dəyişəndən istifadəni bitirdikdən sonra bu yaddaş silmə operatorundan istifadə etməklə boşaldılmalıdır. C++ dilində bu, ilkin şərtdir. Əgər yaddaşı boşaltmasanız, yaddaş ayrılmış (məşğul) qalacaq, lakin heç bir proqram ondan istifadə edə bilməyəcək. Bu vəziyyətdə baş verəcəkdir "yaddaş sızması" (yaddaş sızması).

Java və C# proqramlaşdırma dillərində, ayrıldıqdan sonra yaddaşı boşaltmağa ehtiyac yoxdur. Bunu "zibil yığan" edir.

Tək dəyişən üçün silmə operatorunun ümumi forması belədir:

ptrName silin;

Harada ptrName– yeni operatordan istifadə edərək əvvəllər yaddaşın ayrıldığı göstəricinin adı. Silmə operatorunu yerinə yetirdikdən sonra göstərici ptrName qorunmayan (ayrılmayan) yaddaşın ixtiyari sahəsinə işarə edir.

5. Əsas növ göstəricilər üçün yaddaşın ayrılması (yeni) və boşaldılması (silmək) nümunələri

Nümunələr new və delete operatorlarının istifadəsini nümayiş etdirir. Nümunələr sadələşdirilmişdir.

Misal 1. int yazmaq üçün göstərici. Ən sadə misal

// yeni operatordan istifadə edərək yaddaşın ayrılması int * p; // int-ə göstərici p = yeni int; // göstərici üçün yaddaş ayırın*p = 25; // yaddaşa dəyərlər yazmaq // göstərici üçün ayrılmış yaddaşdan istifadə int d; d = *p; // d = 25 // göstərici üçün ayrılmış yaddaşı azad edin - məcburi sil p;

Misal 2. Double yazmaq üçün göstərici

// ikiqat üçün göstərici üçün yaddaş ayırın double * pd = NULL; pd = yeni ikiqat; // yaddaş ayırın if (pd!=NULL ) ( *pd = 10.89; // dəyərlər yazın ikiqat d = *pd; // d = 10.89 - proqramda istifadə edin // boş yaddaş pd silin; )
6. “Yaddaş sızması” nədir?

« Yaddaş sızması" - bu, dəyişən üçün yaddaşın yeni operator tərəfindən ayrılması və proqramın sonunda silmə operatoru tərəfindən boşaldılmamasıdır. Bu vəziyyətdə, sistemdəki yaddaş işğal altında qalır, baxmayaraq ki, ondan istifadə etməyə ehtiyac yoxdur, çünki onu istifadə edən proqram çoxdan öz işini başa çatdırmışdır.

"Yaddaş sızması" tipik bir proqramçı səhvidir. Əgər “yaddaş sızması” dəfələrlə təkrarlanırsa, o zaman kompüterdəki bütün mövcud yaddaşın “zəbt edilməsi” mümkündür. Bu, əməliyyat sisteminin gözlənilməz nəticələrinə gətirib çıxaracaq.

7. Yaddaşın ayrıla bilməyəcəyi kritik vəziyyəti tutaraq, yeni operatordan istifadə edərək yaddaşı necə ayırmaq olar? Bad_alloc istisnası. Misal

Yeni operatordan istifadə edərkən yaddaşın ayrılmaması mümkündür. Yaddaş aşağıdakı hallarda ayrıla bilməz:

  • boş yaddaş olmadıqda;
  • boş yaddaşın ölçüsü yeni operatorda göstəriləndən azdır.

Bu halda, bad_alloc istisnası atılır. Proqram bu vəziyyətin qarşısını ala və müvafiq olaraq idarə edə bilər.

Misal. Nümunə yaddaşın yeni operator tərəfindən ayrıla bilməyəcəyi vəziyyəti nəzərə alır. Bu halda yaddaşı ayırmağa cəhd edilir. Əgər cəhd uğurlu olarsa, proqram davam edir. Əgər cəhd uğursuz olarsa, funksiya -1 kodu ilə çıxış edir.

int main() ( // üzmək üçün göstəricilər massivini elan edin float * ptrArray; cəhd ( // 10 float elementi üçün yaddaş ayırmağa çalışın ptrArray = yeni float ; ) tutmaq (bad_alloc ba) ( cout<< << endl; cout << ba.what() << endl; return -1; // çıxış funksiyası } // hər şey qaydasındadırsa, massivdən istifadə edinüçün (int i = 0; i< 10; i++) ptrArray[i] = i * i + 3; int d = ptrArray; cout << d << endl; delete ptrArray; // massiv üçün ayrılmış boş yaddaş 0 qaytarmaq; )
8. Eyni vaxtda inisializasiya ilə dəyişən üçün yaddaşın ayrılması. Ümumi forma. Misal

Tək dəyişən üçün yeni yaddaş ayırma operatoru həmin dəyişənin dəyəri ilə eyni vaxtda işə salınmağa imkan verir.

Ümumiyyətlə, eyni vaxtda başlatma ilə dəyişən üçün yaddaş ayrılması belə görünür

ptrName= yeni tip( dəyər)
  • ptrName– yaddaşın ayrıldığı göstərici dəyişənin adı;
  • növü– göstəricinin göstərdiyi növ ptrName ;
  • dəyər– ayrılmış yaddaş sahəsi üçün təyin olunan dəyər (göstərici dəyəri).

Misal. Dəyişənlər üçün yaddaşın eyni vaxtda işə salınması ilə ayrılması. Aşağıda konsol tətbiqi üçün main() funksiyası verilmişdir. Eyni vaxtda başlatma ilə nümayiş etdirilən yaddaş ayrılması. O, həmçinin yaddaşın ayrılması cəhdinin uğursuz olduğu vəziyyəti də nəzərə alır (kritik vəziyyət bad_alloc).

#include "stdafx.h" #include ad sahəsi std istifadə edərək; int main() ( // eyni vaxtda başlatma ilə yaddaşın ayrılması float * pF; int * pI; char * PC; cəhd ( // eyni vaxtda başlatma ilə dəyişənlər üçün yaddaş ayırmağa çalışın pF = yeni float (3,88); // *pF = 3,88 pI = yeni int (250); // *pI = 250 pC = yeni simvol ("M" ); // *pC = "M" ) tutmaq (bad_alloc ba) ( cout<< "İstisna: Yaddaş ayrılmayıb" << endl; cout << ba.what() << endl; return -1; // çıxış funksiyası } // yaddaş ayrılıbsa, pF, pI, pC göstəricilərindən istifadə edin float f = *pF; // f = 3.88 int i = *pI; // i = 250; char c; c = *pC; // c = "M" // başlatılmış dəyərləri çap edin cout<< "*pF = " << f<< endl; cout << "*pI = " << i << endl; cout << "*pC = " << c << endl; // əvvəllər göstəricilər üçün ayrılmış boş yaddaş pF-ni silin; pI-ni silin; PC-ni silin; 0 qaytarmaq; )

Dinamik yaddaşın ayrılması

Əsas tətbiq problemləri

Null göstərici

Null göstərici, verilmiş göstərici dəyişəninin heç bir obyektə istinad etmədiyini (göstərmədiyini) göstərmək üçün istifadə edilən xüsusi dəyəri saxlayan göstəricidir. Müxtəlif proqramlaşdırma dillərində müxtəlif sabitlərlə təmsil olunur.

·C# və Java dillərində: null

·C və C++ dillərində: 0 və ya NULL makro. Bundan əlavə, C++ 11 standartı null göstəricini göstərmək üçün yeni nullptr açar sözünü təqdim edir.

·Paskal və Ruby dillərində: sıfır

·Komponent Paskal dilində:NIL

·Pythonda: Yoxdur

Göstəriciləri idarə etmək çətindir. Göstəriciyə səhv dəyəri yazmaq olduqca asandır və bu, təkrar istehsalı çətin olan səhvə səbəb ola bilər. Məsələn, siz təsadüfən yaddaşda olan göstəricinin ünvanını dəyişmisiniz və ya məlumat üçün yaddaşı səhv ayırmısınız və sonra sizi sürpriz gözləyə bilər: yalnız proqram daxilində istifadə olunan digər çox vacib dəyişənin üzərinə yazılacaq. Bu vəziyyətdə, səhvi təkrarlamaq sizin üçün çətin olacaq. Səhvin tam olaraq harada olduğunu başa düşmək asan olmayacaq. Və onu aradan qaldırmaq həmişə əhəmiyyətsiz olmayacaq (bəzən proqramın əhəmiyyətli bir hissəsini yenidən yazmalı olacaqsınız).

Bəzi problemləri həll etmək üçün qorunma və sığorta üsulları var:

C dilində göstəriciləri öyrənərək, biz dinamik yaddaşın yerləşdirilməsi imkanlarını kəşf etdik. Bunun mənası nədi? Bu o deməkdir ki, dinamik yaddaş bölgüsü ilə yaddaş kompilyasiya mərhələsində deyil, proqramın icrası mərhələsində qorunur. Və bu, bizə yaddaşı daha səmərəli, əsasən də massivlər üçün ayırmaq imkanı verir. Dinamik yaddaş bölgüsü ilə massivin ölçüsünü əvvəlcədən təyin etmək lazım deyil, xüsusən də massivin hansı ölçüdə olması həmişə məlum olmadığı üçün. Sonra, yaddaşın necə ayrıla biləcəyinə baxaq.

Malloc() funksiyası stdlib.h başlıq faylında müəyyən edilmişdir, o, lazımi yaddaş həcmi ilə göstəriciləri işə salmaq üçün istifadə olunur. Yaddaş maşında işləyən hər hansı proqramlar üçün mövcud olan RAM sektorundan ayrılır. malloc() funksiyasının arqumenti ayrılması lazım olan yaddaş baytlarının sayıdır ki, funksiya yaddaşda ayrılmış bloka göstərici qaytarır; malloc() funksiyası hər hansı digər funksiya kimi işləyir, yeni heç nə yoxdur.

Fərqli məlumat növlərinin fərqli yaddaş tələbləri olduğundan, biz müxtəlif məlumat növləri üçün bayt ölçüsünü necə əldə etməyi öyrənməliyik. Məsələn, bizə int tipli dəyərlər massivi üçün yaddaş bölməsi lazımdır - bu yaddaşın bir ölçüsüdür və əgər eyni ölçülü, lakin char tipli massiv üçün yaddaş ayırmaq lazımdırsa - bu müxtəlif ölçülü. Buna görə yaddaşın ölçüsünü bir şəkildə hesablamalısınız. Bu, ifadəni götürən və ölçüsünü qaytaran sizeof() əməliyyatından istifadə etməklə edilə bilər. Məsələn, sizeof(int) int dəyərini saxlamaq üçün lazım olan baytların sayını qaytaracaq. Bir misala baxaq:


Yandex.Direct


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

Bu nümunədə, in sətir 3 ptrVar göstəricisinə ölçüsü int məlumat növünə uyğun gələn yaddaş yerinə ünvan verilir. Avtomatik olaraq bu yaddaş sahəsi digər proqramlar üçün əlçatmaz olur. Bu o deməkdir ki, ayrılmış yaddaş lazımsız olduqdan sonra o, açıq şəkildə buraxılmalıdır. Yaddaş açıq şəkildə buraxılmırsa, proqram başa çatdıqdan sonra yaddaş əməliyyat sistemi üçün boşalmayacaq, buna yaddaş sızması deyilir. Siz həmçinin null göstəricini ötürməklə ayrılmalı olan ayrılmış yaddaşın ölçüsünü müəyyən edə bilərsiniz, burada bir nümunə:



Gördüyünüz kimi, bu qeyddə çox güclü bir məqam var, biz malloc() funksiyasını sizeof(float) istifadə edərək çağırmamalıyıq. Əvəzində biz malloc()-a float növünə göstərici ötürdük, bu halda ayrılmış yaddaşın ölçüsü avtomatik olaraq özünü müəyyən edəcək!

Göstərici tərifindən uzaq yaddaş ayırmaq lazımdırsa, bu xüsusilə faydalıdır:


float *ptrVar; /* . . . yüz sətir kod */ . . . ptrVar = malloc(sizeof(*ptrVar));

Əgər siz sizeof() əməliyyatı ilə yaddaş ayırma konstruksiyasından istifadə etsəydiniz, onda siz kodda göstəricinin tərifini tapmalı, onun məlumat növünə baxmalı və yalnız bundan sonra yaddaşı düzgün yerləşdirə biləcəksiniz.