C++ da havolalar va ko'rsatkichlar, xotira menejerligini o'rganamiz.

C++ da havolalar va ko'rsatkichlar, xotira menejerligini o'rganamiz.

ᅠ★LEARNER★

Ushbu maqolada biz havola va ko'rsatkichlar, ular qanday ishlaydi va nega kerak, C++ dasturlash tilida qiymatlar qanday qilib xotiraga yoziladi, shu va shunga o'xshash masalalar haqida gaplashamiz.


(!) Maqolaning medium.com da nusxasi mavjud(link)


Agar siz python tiliga qiziqsangiz, o'rganayotgan bo'lsangiz pythonda o'zgaruvchilar xotirada joylashishi haqida FutureDreams tomonidan yozilgan yaxshi bir maqolani o'qishni tavsiya qilaman: https://telegra.ph/Pythonda-dinamika-va-ozgaruvchilar-asosiy-tushunchalar-09-05


Maqola hajmini saqlash uchun kodlarda iostream kutubxonasini yuklash va std dan foydalanish qoldirib ketilgan bo'lishi mumkin, kodni sinayotganda shuni ham e'tiborga olishingizni so'rardim.

O'qish jarayonida sizdan ba'zi boshlang'ich bilimlar, xususan obyektga yo'naltirilgan dasturlash(OOP)dagi ba'zi tushunchalar talab etiladi.


Ko'pchilik C++ ni o'rganayotganlar aynan shu mavzuga kelganda hafsalalari pir bo'ladi. Yoki shunchaki bu mavzuga sayoz qarab, aylanib o'tib ketishlari mumkin. Hatto bu mavzu kimlarnidir C++ ni o'rganishlarini shunchaki to'xtatib qo'yishgacha olib kelgan.

I'm still confused with it! source: https://www.reddit.com/r/ProgrammerHumor/comments/at58sy/point_me_towards_the_exit_please/

Men ham C++ ni endi o'rganayotgan paytlarim ko'rsatkichlar bilan ishlashga tushunmaganman, tashqaridan qaraganda o'zgaruvchilarni oddiy e'lon qilish va dinamik e'lon qilishda deyarli farq yo'qdek edi, men ham yozgan kodlarimda dinamik obyektlardan foydalanmasdim. Chunki mavzu yuzasidan menda yaxshi tushuncha shakllanmagandi, o'zbek tilida yozilgan men o'qigan adabiyotlardagi ma'lumotlar ham meni qoniqtirmasdi. Ko'rsatkichlar nega kerakligi haqida bilmasdim. Keyinroq bular haqida oz-ozdan ma'lumot izlab o'rgana boshladim(hozir ham o'rganyapman), bilganlarimni sizlar bilan imkon qadar bo'lishishga harakat qilaman. Qani yaxshilab o'rnashib, IDE larni sozlab oling, xotira bilan ishlash olamiga kichik sayohat uyushtiramiz :)


Tezkor xotira(RAM)

Tezkor xotira o'zida vaqtinchalik qiymatlarni saqlash uchun ishlatiladi. U xuddi kataklarga bo'lib chiqilganga o'xshaydi, har bir katak 1 baytdan ma'lumotni sig'dira oladi va o'zining unikal(yagona) manziliga ega bo'ladi. Undagi qiymatga murojaat qilishimiz uchun biz o'sha qiymatning xotiradagi manzilini bilishimiz shart. Lekin inson raqamlardan ko'ra so'zlarni osonroq eslab qoladi. Shundan kelib chiqib, dasturlashda qiymatlarning xotiradagi manziliga nom biriktirish fikri tug'ilgan.

int birthday = 1;

yuqoridagi holda birthday kompilyatsiyadan so'ng qandaydir manzilga almashtiriladi. Ba'zi insonlarda "o'zgaruvchiga nomni qisqa bersam, dasturim tezroq ishlaydi" degan xato fikr o'rnashib qolgan bo'ladi, lekin o'zgaruvchi nomini 1 ta harf yoki kompilyator tomonidan belgilangan maksimal uzunlikda nomlash ahamiyatga ega emas.

Lokal o'zgaruvchilar

C++ da figurali qavslar({}) orasida e'lon qilingan o'zgaruvchilar faqat o'sha yerda "yashashini" bilsangiz kerak(Albatta, bunda ba'zi istisnolar mavjud, masalan static kalit so'zi bilan e'lon qilinadigan o'zgaruvchilar). Buni aniqlash uchun bizga kuchli debugging dasturlar shartmas, shunchaki kichik kod yozamiz:

class Test{
public:
Test(){}
~Test(){cout<<"Obyekt o'chirildi!\n";}
};
int main(){
Test t;
return 0;
}

Kodni yurgazib ko'rgach, obyekt o'chirilgani haqidagi ma'lumotni ko'rasiz(kodni tushunmasangiz, C++ da obyektga yo'naltirilgan dasturlash, destruktorlar haqida o'qib ko'rib, maqolani kelgan joyidan davom ettirishingiz mumkin). Biz shu va shundan keyingi namunalarda konstruktor/desktruktordan obyekt xotiraga joylashgani/o'chirilgani haqida signal sifatida ishlatamiz. Bunda biz e'lon qilgan o'zgaruvchimiz RAMga qanday yozilyapti?

Stack

Stack(o'qilishi Stek) - ma'lumotlarni tezkor xotiraga(keyinchalik qisqalik uchun RAM deb ketaman) joylash usuli deyishim mumkin. U LIFO(last in - first out) shaklida ishlaydi, gap siz ma'lumotlar tuzilmalari(data structures)da ko'rgan stack haqida emas, lekin ularning ishlash prinsiplari bir-xil. Stackning ba'zi xususiyatlari:

  • Dastur ishga tushgach, u uchun alohida stack beriladi
  • Stack o'lcham jihatdan chegaralangan bo'ladi
  • Qiymatlar kiritilishida ancha tez
  • Lokal o'zgaruvchilar, qaytish qiymatlar va funksiyaga parametrlarni uzatishda foydalaniladi
  • Xotiradan joy olish chiziqli tartibda amalga oshiriladi
  • Hajm jihatdan stack chegaralangan, shuning uchun sizga qancha joy kerakligini va u stackning maksimal hajmidan kichikligini bilgan holda undan foydalanishingiz mumkin
  • Dastur ishini tugatgach, stack avtomatik ravishda tozalab tashlanadi.

Ha, yuqoridagi t o'zgaruvchimiz ham stackka kiritilgan deb ayta olamiz. Evrika! Dinamik bo'lmagan o'zgaruvchilar stackka joylanib, ish tugagach avtomatik o'chirib yuborilarkan-da deyishga shoshmang, yuqoridagi namunaning 7-qatori(t e'lon qilingan yeri)ni 5-qatordan keyinga ko'chirib, kodni yurgazib ko'ring ;) Stack xususiyatlariga ko'ra, unga lokal o'zgaruvchilar joylanadi, hozirgi aytgan holatimda esa t o'zgaruvchisi global bo'lib qoladi. Uni qayerga berkinib olgani haqida quyiroqda gaplashib o'tamiz.

Stack o'lchami chegaralangan, agar u to'lsa dastur stackoverflow(stack to'lib ketish) xatoligi tufayli crash ga uchraydi. Buni sinab ko'rishga kichik kod yozib ko'ramizmi?

int a[524288];//256*1024*2, bu kod menda xatolik bergan, xohlasangiz a hajmini kattaroq qilib ko'ring
Dastur noma'lum xatolikka uchradi


Stackning chegarasi 1MBlar atrofida bo'ladi, bu narsa OS(operatsion sistema) tomonidan belgilangan bo'ladi. Stackoverflowga yana bir sabab esa funksiya chaqirilganda u ishini tugatmasdan boshqa funksiya(lar)ni, boshqalari ham ishini tugatmasdan yana funksiyalar chaqirish holati. Bunday holat rekursiyada uchraydi, rekursiyadagi funksiyaning o'ziga-o'zi murojaatlar soni oshib ketib, qaytadigan qiymatlar bilan stack to'lib ketishi misol bo'la oladi(Bu haqida batafsil: Rekursiya - C++ da).

Stackdagi xotiradan joy olish chiziqli tartibda amalga oshiriladi. Ya'ni siz biror o'zgaruvchi e'lon qilib, keyin yana boshqa o'zgaruvchi e'lon qilganingizda ular xotirada bir-birlariga "qo'shni" bo'lib qoladi. Buni ham sodda kod bilan test qilib ko'ramiz:

int a, b;
cout<<&a<<endl<<&b<<endl;
cout<<(int)&a-(int)&b;

Bunda a va b ning manzillari va ularning ayirmasi ekranga chiqariladi, mening holatimda manzillar quyidagicha://sizda manzillar boshqacha chiqishi mumkin

0x22feac

0x22fea8

4

Ayirma 4 chiqqani sababi a 4 birlikni egallaydi, b esa a ning bosh yacheykasidan 4 birlik keyin joylashadi. Agar & belgisi sizga notanish bo'lsa hozir bu haqida ham gaplashamiz.


Havolalar(inglizcha atamasi references)

Xo'sh, yuqorida qiymatlarni xotirada saqlanish yo'li haqida ozroq tushunchaga ega bo'ldik deb o'ylayman. Demak qiymat xotiraning biror yacheykasiga yozilsa, o'sha yacheykaga murojaat qilib uni o'zgartirish orqali o'zgaruvchimizni o'zgartirsak bo'ladimi? Albatta, o'zgaruvchining manzilini olishimiz uchun o'zgaruvchi nomidan oldin &(ampersanta) belgisini ishlatishimiz kerak. Ikkita o'zgaruvchimizni bitta yacheykaga bog'lash orqali yuqoridagi savolga javob olamiz:

Sintaksis jihatdan kodimiz quyidagicha bo'ladi:

int a = 7;
int &b = a;
//yoki qisqarog'i:
//int a = 7, &b = a;

albatta bunda yuqoridagi ikki o'zgaruvchidan biri o'zgarishi 2-siga ham ta'sirini o'tkazadi. Bu orqali masalan biz ikkita o'zgaruvchining qiymatlarini almashtiradigan swap funksiyasini tuza olamiz:

void swap(int &x, int &y)
{
x += y;
y = x - y;
x -= y;
}
int main(){
int a = 5, b = 7;
swap(a, b);
cout<<a<<endl<<b;
return 0;
}

Havolalar bilan ishlashda yana bir qulay tarafi shunda-ki, u bilan biz resurslarimizni tejab qolishimiz mumkin. Tasavvur qiling, bizda shunday katta ma'lumotlar to'plangan class bor, uni funksiyaga parametr sifatida berishimiz kerak, funksiyamiz uni ichidagilarini ekranga chiqarib beradi. Agar oddiy tarzda bersak, xotirada yana bir xuddi shunday ma'lumotlarni o'zida jamlagan class uchun joy ajratiladi, bu esa protsessor vaqti va xotiradagi joy uvol bo'ladi degani. Biz uni havola qilib bersak, uni yangidan xotiraga joylash o'rniga tayyor manzilini olamiz, const kalit so'zi yordamida uni funksiya ichidagi o'zgarishlardan saqlab qolamiz:

void chopEt(const &MyType a)//kodlar

Yoki masalan massivga qiymat kiritishni soddalashtirib+yengillashtirib olishimiz mumkin:

int a[10];
for(int &x: a) cin>>a;

Menimcha havola haqida tushunib oldingiz. Endi dinamik obyektlar bilan ishlash - ko'rsatkichlar haqida suhbatni davom ettirsak.

Heap

Heap ham xotiraga qiymat yozish usuli deyishim mumkin. U odatda stackdan farqli ravishda OS ishga tushganda RAM ga joylanadi(Gap ma'lumotlar tuzilmalaridagi heap haqida emas), har bir dastur uchun alohida beriladi.

  • OS ishga tushganda RAMdam joy oladi
  • Hajmi stackka qaraganda anchagina katta bo'ladi, dinamik kengaya oladi
  • Ko'p zamonaviy OSlarda, har bitta protsess o'zining alohida heap xotirasiga ega bo'ladi
  • Qiymatlar kiritilishi stackka qaraganda sekinroq
  • Dinamik obyektlar undan joy oladi
  • Qiymatlar xotiradan iyerarxik va tasodifiy tarzda joy oladi
  • Unga qiymat joylash va o'chirish dasturchi zimmasiga yuklatiladi
  • Ko'pgina zamonaviy OSlarda va ko'p hollarda, dastur ishini tugatgach, heapga joylagan qiymatlari o'chib ketadi

Heap shunchaki(mantiqiy) xotira bo'lib, RAM ning bir qismi heap sifatida ishlatilishi mumkin. RAMning bir qismini heap sifatida ishlatishni OSga qo'yib bering, bizni dinamik o'zgaruvchilar qiziqtiradi.

Heapdagi iyerarxik joylashuv, stackoverflowdagi Martin Liversage javobidan olindi: https://stackoverflow.com/a/1213360

Eng birinchi yozgan yuqoridagi kodimizning main funksiyasi ichiga kichik o'zgartirish kiritamiz:

Test *t = new Test();
return 0;

Hech narsa chiqmadimi? Chunki biz obyektimizni dinamik tarzda joylayapmiz, shunday ekan uni o'zimiz o'chirishimiz kerak bo'ladi. Lekin deyarli barcha zamonaviy OSlar dastur ishini yakunlagach unga ajratilgan heap ni tozalaydi, lekin dastur ishini yakunlab bo'lgach tozalashni OS qilgani uchun bizga "Obyekt o'chirildi" degan yozuv chiqmaydi.

b stackka yozilgan qiymatga ko'rsatkich bo'lib, heapdagi manzilni o'zida saqlashi


Qiymatlarni heapga joylash va o'chirish

Biz heapga qiymat joylash uchun yuqoridagi kabi o'zgaruvchini initializatsiya qilib olamiz. E'tibor bering: new kalit so'zi yordamida obyektni konstruktoriga murojaat qilyapmiz, so'ngra u heapga joylanyapti. Obyekt heap ga joylanadi, unga ko'rsatkich stackda turadi. Buni stackning yuqorida aytilgan "qo'shnilik" xususiyatini bilgan holda test qilish uchun kod yozsakchi?

int a;
int *b = new int(6);
cout<<"a ning manzili: "<<&a<<endl;
cout<<"b ning manzili: "<<&b<<endl;
cout<<"b ko'rsatkich bo'lib turgan obyektning heap dagi manzili: "<<b;

bundan siz a va b ning qo'shni ekanligini va b ulangan heapdagi obyekt manzilini ko'rasiz, masalan mening holatimda bu quyidagicha:

Agar bitta obyektni yoki massivni heapga joylamoqchi bo'lsangiz

long long int *son = new long long int(5);//bitta son uchun
long long int *sonlar = new long long int[10];//massiv uchun
  • E'tibor bering, yuqorida new kalit so'zi yordamida joylanganda konstruktor ishga tushadi dedik. N o'lchamli massivni joylaganimizda ham o'sha tipdagi qiymatlar uchun N marta konstruktor ishga tushib, heapga qiymatlar joylanadi.

Yoki avval ko'rsatkich ochib, keyinroq heapga qiymat joylab boyagi ko'rsatkichga biriktirib olsangiz ham bo'ladi:

long long int *son = NULL;
son = new long long int(1234);

Ko'rsatkich orqali heapdan qiymat olishda ko'rsatkich nomi oldiga yulduzcha(*) belgisini yozishimiz kerak:

cout<<*son;

cout<<son; bo'lganda u heapdagi manzilni ko'rsatadi, cout<<&*son; da ham shunday.

*son = 1234; shaklida yozish orqali qiymatini o'zgartiramiz.

  • Ko'rsatkichni ko'rsatkichga qiymat sifatida berish quyidagicha:
int* a = new int(4), *b = new int(5);
a = b;//albatta bunda biri o'zgarishi ikkinchisiga ta'sir qiladi
//chunki ular ko'rsatayotgan heapga manzil bir xil
//alohida bo'lsin desangiz *a = *b deyishingiz mumkin masalan
  • Joylagan obyektimiz ichidagilarga ko'rsatkich(->, inglizchasiga arrow) orqali murojaat qilamiz:
class Obj{
public:
int a, b;
Obj(){a = 2; b = 3;}
};
int main(){
Obj *var = new Obj();
cout<<var->a;//2
delete var;
return 0;
}
  • Heapdan bitta qiymat o'chirish uchun delete, massivni o'chirish uchun esa delete[] ishlatiladi. delete bitta obyekt uchun desktruktorga murojaat qilib, uni xotiradan o'chirsa, delete[] massivdagi har bir element uchun destruktorini ishlatib, elementlarni o'chirib tashlaydi.
class Test{
public:
int a,b,c;
Test(){cout<<"Obyekt xotiraga kiritildi\n";}
~Test(){cout<<"Obyekt o'chirildi!\n";}
};
int main(){
Test *t = new Test[2];//2 marta konstruktor ishga tushadi
delete t;//1 ta destruktor ishlaganini ko'ramiz. delete[] bilan o'chirish kerak
return 0;}

delete yordamida heap tozalanadi, lekin ko'rsatkich stackdaligi uchun unga ta'sir o'tkazmaydi.

Bir qator bilan bir nechta dinamik obyektlarni o'chirish uchun

delete a,b,c;//massivlar uchun delete[] ishlatgan ma'qul

kabi yoziladi.

Yana bir qiziq holat ko'rsataman:

long long int a = 7;
long long int *b = &a;//bunda b a ga ko'rsatkich bo'lib qoladi
delete b;
cout<<*b;

Kodni avval yozib, yurgazib ko'ring. Shunda b hali ham 7 ekanligini ko'rasiz. Nega bunday bo'ldi? Sababi biz b ga manzil ko'rsatayotganda unga heapdagi manzilni emas, stackdagi manzil(a ning manzili)ni berdik. Bunda stackdagi qiymat o'chirilmaydi, o'zi shundog'am dastur ishini tugatgach o'chib ketadi.

  • Funksiyalar ham ko'rsatkich qaytarishlari mumkin. Ko'rsatkich qaytaradigan funksiyalarni yozish uchun sintaksis quyidagicha:
tur* funksiya_nomi(/*parametrlar*/){
//kodlar
return ko'rsatkich;
}

Ko'rsatkichlar heapga manzil saqlashi kerak, ularning stackdagi o'lchami 32-bitlik OSlarda odatda 4 baytlik bo'ladi, chunki bu xotiraning manzillanishi bilan bog'liq. 64 bitlik OSlarda ko'rsatkichlar stackdan 8 baytlik joyni band qiladi.

Ko'rsatkichlar arifmetikasi

Ko'rsatkichlarga inkrement/dekrement ni qo'llash orqali u ko'rsatayotgan manzilni siljita olamiz. Manzil ko'rsatkich tipichalik hajmda suriladi, masalan:

int *son = new int(5);
cout<<++son<<endl<<son;

bunda ko'rsatkich ko'rsatayotgan manzil 4 baytga(int hajmichalik) surilganini ko'rasiz. Ko'rsatkichga son qo'shib surishimiz ham mumkin: ptr+son; bunda manzil ptr_turi*son chalik suriladi.

int *ptr;
ptr+5;//ptr avvalgi joyidan 4*5 birlikka suriladi

Umuman bu narsa massivlarda qo'l kelishi ham mumkin:

int arr[] = {1,2,3,4,5,6};
for(int* ptr = arr; ptr<arr+sizeof(arr)/sizeof(int); ptr++)
cout<<*ptr<<" - ";

Dinamik xotira bilan ishlayotganda ko'rsatkich ustida qo'shish/ayrishlarni bajarishda ehtiyot bo'lgan yaxshi. Umuman, bu unchalik ham yaxshi fikr bo'lmasligi mumkin - agar ko'rsatkichni to'g'ridan-to'g'ri inkrement/dekrement qilsangiz avvalgi joyni yo'qotib qo'yishingiz, yoki kodni o'qish uchun biroz noqulay qilib qo'yishingiz mumkin.

Memory Leakage

Stack haqida gaplashganimizdek, "Obyektlarni heapga joylash va o'chirish" bo'limi 1-namunadagi kodda main ishini tugatgach, heap dagi obyektga ko'rsatkich bo'lib turgan b ham o'chib ketadi, lekin heapdagi qiymat emas. Odatda juda ko'p zamonaviy OSlarda dastur ishini tugatgach heap OS tomonidan bo'shatiladi. Lekin agar dastur o'z ishini davom ettirishi kerak bo'lsachi? Masalan biror funksiya ichida dinamik o'zgaruvchilar ishlatib, uni o'chirmasdan ketdik:

{
long long int *a = new int(2341);
long long int *b = new long long int[11];
//kodlar
...
}

va bu bilan biz 96 baytlik xotirani ishlatmasak ham band qilib qo'ydik. Namunada biz heapdagi qiymatimizga ko'rsatkichni yo'qotamiz(chunki funksiya ishini tugatgach, ko'rsatkich ham stackdan o'chiriladi), natijada heapdagi qiymatga murojaat qila olmaymiz. Dastur ishini tugatmagani uchun qiymat heapda turaveradi. Hozirgi holatda bizda bekorchi 96 bayt joy egallagan xotira bor. Tanishing - bu holat inglizchasiga memory leakage deyiladi(o'zbekchaga chiroyli tarjima qilolmadim, shuning uchun atamani shundayligicha yozib ketaman). Ko'rsatkichimiz funksiya ishini bajarib bo'lgach, o'chib ketdi. Bizda ozgina band bo'lgan bekorchi joy bor, lekin unga qayta yozolmaymiz, chunki uni tozalab tashlash kerak, biz tozalamaganimiz uchun OS uni band deb ko'radi va bizga qayta foydalanishga bermaydi. Bu kichik holat, lekin agar kattaroq kodlar bilan ishlash bo'lganda-chi, klasslarimiz kattaroq ma'lumotlar bilan bo'lsa-chi?

uzr, fotoshopga yo'qroqman :) rasmga yordam berganlari uchun @ThereisnoKNOWLEDGEthatISnotPOWER ga alohida rahmat


Oddiy, qisqa kodlarda bu holat u qadar xavflimas. Lekin gap kattaroq hajmdagi dinamik obyektlar bilan ishlaydigan kodlar haqida ketganda, dastur ishini tugatmasdan oldin heap ni to'lib qolishi(heapoverflow)ga olib kelishi, ba'zida OSni crashga uchratishi ham mumkin. Menimcha, ularni o'chirib yurish yaxshi odat.

Dangling/Wild pointers

Wild pointers - heapdagi biror xotiraga qiymat joylamasdan oldin o'sha manzilga qo'yilgan ko'rsatkich, masalan

long long int *son;

son wild pointer ga misol.

Dangling pointers - heapdagi qiymat o'chirib yuborilgach, ko'rsatkich hali ham o'sha manzilga ishora qilib turgan bo'lsa u dangling pointers deyiladi(delete qilinganda heapdagi qiymatlar o'chiriladi, lekin stackdagi ko'rsatkich NULL olmaydi):

int *a = new int(5);//normal ko'rsatkich
delete a;//xotira tozalandi, lekin a hali ham o'sha xotiraga ko'rsatkichligicha qolmoqda

Tozalangan xotira OS tomonidan yana boshqa maqsadlarda ishlatilinishi mumkin. Masalan sizning kodingizdagi boshqa bir turdagi ko'rsatkichni "qo'liga tushib" qolsa-chi? Yoki boshqa maqsadda OS undan foydalanishi, boshqalarga berib yuborishi mumkin. Muammo dastur ishlayotganda shunday ko'rsatkichlar paydo bo'lishida emas, muammo ulardan foydalanishda kelib chiqishi mumkin. Albatta, bunday chalkashliklar kamdan-kam kuzatiladi. Bunday holatlarni oldini olish uchun ko'rsatkichga NULL biriktirib qo'yamiz:

delete a;
a = NULL;

Agar kodni xavfsizroq+tozaroq qilmoqchi bo'lsangiz DRY(Don't Repeat Yourself) ga asoslanib, ko'rsatkichni o'chirib unga NULL biriktirib qo'yadigan kichkinagina funksiyacha yozib olsangiz bo'ladi.

batafsil wikida: https://en.wikipedia.org/wiki/Dangling_pointer

Heapga qiymat kiritishning boshqa yo'llari[ixtiyoriy, bonus]

Albatta, bu yerda new dan tashqari calloc va malloclardan ham qiymat kiritishda foydalaniladi. new C++ da tanishtirilgan, u obyektni heapga joylayotganda konstruktorga murojaat qiladi lekin malloc/calloc esa aksincha. Bu ikki funksiyani stdlib.h dan topishingiz mumkin.

malloc()

yoki memory allocation, dinamik qiymat uchun heapdan joy ajratadi va unga ko'rsatkich qaytaradi. Ko'rsatkich void tipida bo'ladi, sintaksisi:

(tur*)malloc(bayt)

Agar xotiradan berilgan o'lchamdagi joyni ololmasa, NULL ni qaytaradi. U orqali xotiradan joy bron qilganimizda, avval ko'rsatkichni NULL emasligini tekshirib olishimiz kerak:

int *a((int*)malloc(sizeof(int)));// = ni o'rniga shunday biriktirsa ham bo'ladi
if(a!=NULL) *a = 4;
//kodlar

free(a);

malloc xotiradan joy olganda xotiradagi eski qiymatlarni o'zgartirmaydi.

sizeof(int) - int hajmini qaytaradi, bunda 4 bayt joy egallaydi. sizeof(int)*10 qilsak 10*4 = 40 baytlik joyni egallaydi, biz undan massiv sifatida foydalanishimiz mumkin.

calloc()

yoki contiguous allocation. Bu ham xotiradan berilgan sig'im bo'yicha joy ajratadi. malloc dan farqi shundaki, u ajratilgan xotirani avtomatik nollaydi. Asosan C da massivlar va strukturalar uchun ishlatiladi. Sintaksisi:

(tur*)calloc(soni, baytlar);

xotiradan baytlar*soni hajmda joy ajratadi. malloc xotiradan bitta katta blokni egallasa, calloc xotiradan "baytlar" o'lchamichalik "soni"ta blok oladi va har birini nollaydi.(@mumtozbekov ga mavzu bo'yicha bergan javoblari va tushunchalari uchun rahmat)

 

realloc()

Siz heapga massiv joylab, massivdagi ma'lumotlarni yo'qotmasdan uni dinamik kengaytirmoqchisiz? realloc shu xizmatni sizga bajarib beradi. Masalan:

int *massiv = (int*)malloc(10*sizeof(int));
//massiv hajmini 10dan 15 ga oshiramiz
massiv = (int*)realloc(massiv, 15*sizeof(int));
//kodlar

free(massiv);

realloc qayerdandir yangi hajmchalik joy topib, eski qiymatlarni o'sha yoqqa o'tkazvoradi, shu bilan birga qolib ketayotgan eski joyni tozalab qo'yadi va yangi manzilga ko'rsatkich qaytaradi.

free orqali xotiradagi qiymatlarni tozalab tashlaymiz. new va free ni, malloc/calloc va delete ni birga ishlatish tavsiya etilmaydi, u har xil kutilmagan natijalarni keltirib chiqarishi mumkin. Yaxshisi new bilan obyektni joyladingizmi, delete bilan o'chiring. malloc/calloc ishlatganingizda esa free yordamida heapni tozalang. malloc calloc ga qaraganda tezroq ishlaydi, lekin calloc nisbatan xavfsizroq.

malloc/calloc lar asosan C da ishlatiladi, C++ da new dan foydalanishni maslahat berishadi. Sababi C++ da classlardan foydalaniladi, obyektlarni heapga to'g'ri kiritishda new yaxshi, qolaversa u konstruktorni ham ishga soladi.

Ko'rsatkichga ko'rsatkich(pointer to pointer) haqida qisqacha

Bu mavzuda C++ o'rganayotganim uchun ochig'i kam ma'lumotga egaman, Bu narsa C++ga qaraganda C da ko'p ishlatiladi. Masalan, string tipi "char*"(dinamik kengayuvchi char massiv) dan foydalanadi, siz agar *string qilib ishlatmoqchi bo'lsangiz, bu ish ortida siz "char**" tipida ishlatayotgan bo'lasiz. Quyida soddaroq kod keltirilgan:

int a = 10;
int *b = &a;
int **c = &b;
**c = 20;
cout<<a<<endl<<*b<<endl<<**c<<endl;

va ekranda uchchalasi ham 20 ga teng ekanligini ko'rasiz, c ni e'lon qilgan joyimizni pastidan yana int ***d = &c; deb davom etib ketishimiz mumkin.

Xulosa

Biz xotiraga qiymat saqlanishining 2 xil turi: stack va heap haqida gaplashdik. Ularning ishlashdagi farqlarini xususiyatlaridan ko'rgan bo'lsangiz kerak. Qisqagina xuloasalar:

  • Stackka qiymat kiritish heapga yozishdan ko'ra tezroq
  • Stack cheklangan hajmga ega, lekin heap dinamik tarzda kengaya oladi
  • Stackka qiymatlar joylash chiziqli, heapga qiymatlar joylash tasodifiy tarzda amalga oshiriladi
  • Stackka kiritiladigan ma'lumotlar stack sig'imidan oshib ketsa, stackoverflow xatoligi yuzaga keladi, bunday holatlar rekursiyada kuzatilishi mumkin. Bunda heapdan foydalangan yaxshi
  • Agar heap ga yozilgan ma'lumotlar tozalab turilmasdan, dastur ishlashda davom etib yana dinamik obyektlar e'lon qilaversa va ularni o'chirmasa, dastur ishlash jarayonida heapoverflow xatoligi yuzaga kelishi mumkin
  • Ko'rsatkich boshqa xotira manzilini o'zida saqlaydigan, stackka joylangan qiymat
  • Memory leakage ni oldini olish uchun dinamik obyektlarni o'chirib yurish yaxshi odat
  • Agar funksiyaga parametr sifatida beradigan o'zgaruvchimiz funksiya ichida o'zgartirilmasa, unda uni havolasini bergan ma'qul
  • 4 baytdan kichik yoki unga teng qiymatlarni heapga kiritish unchalik yaxshi fikrmas, chunki 32 bitlik OSlarda ko'rsatkich stackdan 4 bayt oladi.

Maqola boshidagi kodimizda Test tipidagi t ni global qilganimizda qayoqqa berkinib olgani sizga qiziqmi? U na stackka, va na heapga joylangan, javobi quyidagi rasmda

source: https://icarus.cs.weber.edu/~dab/cs1410/textbook/4.Pointers/memory.html



Shu joyiga kelganda siz bilan uyushtirgan sayohatimiz ham tugadi. Agar yaxshi gidlik qila olgan bo'lsam bundan xursandman, zeriktirib qo'ymadim degan umiddaman ;).

Quyida kichik kod qoldiraman, nega xato ishlashi mumkinligi haqida o'ylab ko'ring:

int* createArray(int size)
{
int array[size] = {0};//massiv barcha elementlarini nollaymiz
return array;
}
int main(){
int *arr = createArray(4);
for(int i = 0; i<4; i++)
cout<<arr[i]<<endl;
return 0;
}

Aytgancha, sayohat boshida kodlarimda ko'rsatkichlardan foydalanmasdim degandim, hozir ham foydalanmayman :) albatta agar koddagi o'zgaruvchilar kattaroq joy talab qilmasa


Mavzu yuzasidan muhokama qiladiganlarimiz shular edi. Agar siz maqoladagi ma'lumotlarda kamchiliklar topsangiz, yoki sizda shu mavzu yuzasidan yuqorida aytilmagan ma'lumotlar bo'lsa maqola muallifiga murojaat qiling, ismingiz maqolada ko'rsatib o'tiladi.

Mavzu bo'yicha shu maqoladagi ma'lumotlar bilan cheklanib qolmasdan qo'shimcha sifatida boshqa manbaalarga ham murojaat qilishingizni so'rab qolaman.

Savollar bo'lsa telegramdagi @C_Genius yoki @cppuz guruhlariga kirib mavzu yuzasidan tushunmagan narsangizni so'rashingiz, yoki muhokama qilishingiz mumkin

Foydalanilgan manbalar:

Report Page