14-bo‘lim

Dinamik xotira

Stek va uyum farqi, malloc, calloc, realloc va free, xotira sizishi, ikki marta ozod qilish va osilib qolgan ko'rsatkichlar.

🕑 17 daqiqa o‘qish 📄 808 so‘z 👁 3 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Stek va uyum
  2. malloc - xotira so'rash
  3. calloc - nol bilan to'ldirilgan xotira
  4. realloc - hajmni o'zgartirish
  5. O'sib boruvchi massiv
  6. Foydalanuvchi bergan hajm
  7. Ikki o'lchamli dinamik massiv
  8. Xotira sizishi
  9. Xotirani tekshirish vositalari
  10. Funksiyadan xotira qaytarish
  11. Qoidalar to'plami
  12. Xulosa

Shu paytgacha massiv hajmini oldindan yozib keldik. Ammo hajm faqat dastur ishlayotganda ma'lum bo'lsa-chi?

Stek va uyum #

Dastur xotirasining ikki asosiy sohasi Stek (stack) ✔ Juda tez ✔ Avtomatik tozalanadi ✔ Hajmi kompilyatsiyada ma'lum ✖ Kichik (odatda 1-8 MB) ✖ Funksiya tugagach yo'qoladi int massiv[100]; Uyum (heap) ✔ Katta (RAM hajmicha) ✔ Hajm ish paytida aniqlanadi ✔ Funksiyadan keyin ham yashaydi ✖ Sekinroq ✖ O'zingiz tozalashingiz kerak malloc(n * sizeof(int)); Hajm oldindan noma'lum bo'lsa - uyum kerak
Stek avtomatik, uyum esa qo'lda boshqariladi

malloc - xotira so'rash #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int soni = 5;

    int *massiv = malloc(soni * sizeof(int));

    if (massiv == NULL) {
        printf("Xotira ajratilmadi!\n");
        return 1;
    }

    for (int i = 0; i < soni; i++) {
        massiv[i] = (i + 1) * 10;
    }

    for (int i = 0; i < soni; i++) {
        printf("massiv[%d] = %d\n", i, massiv[i]);
    }

    free(massiv);        /* xotirani qaytaramiz */
    massiv = NULL;       /* ko'rsatkichni tozalaymiz */

    return 0;
}
Natija
massiv[0] = 10
massiv[1] = 20
massiv[2] = 30
massiv[3] = 40
massiv[4] = 50
malloc ni to'g'ri yozish
C
int *massiv = malloc(soni * sizeof(int));        /* yaxshi */
int *massiv = malloc(soni * sizeof(*massiv));    /* eng yaxshi */

Ikkinchi shakl afzalroq: turni o'zgartirsangiz (int dan long ga), sizeof avtomatik moslashadi va xato qilish imkoni yo'qoladi.

C da malloc natijasini aylantirish shart emas:

C
int *m = (int *) malloc(...);    /* keraksiz */
int *m = malloc(...);            /* to'g'ri */

Aylantirish stdlib.h ni unutgan holatdagi xatoni yashirib qo'yishi mumkin.

malloc natijasini doim tekshiring
C
int *massiv = malloc(1000000000L * sizeof(int));

if (massiv == NULL) {
    fprintf(stderr, "Xotira yetarli emas\n");
    return 1;
}

Xotira tugagan bo'lsa, malloc NULL qaytaradi. Tekshirmasdan ishlatsangiz - segmentation fault.

calloc - nol bilan to'ldirilgan xotira #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int soni = 5;

    int *m1 = malloc(soni * sizeof(int));    /* axlat qiymatlar */
    int *m2 = calloc(soni, sizeof(int));     /* hammasi nol */

    if (m1 == NULL || m2 == NULL) {
        free(m1);
        free(m2);
        return 1;
    }

    printf("calloc bergan qiymatlar: ");
    for (int i = 0; i < soni; i++) {
        printf("%d ", m2[i]);
    }
    printf("\n");

    free(m1);
    free(m2);

    return 0;
}
Natija
calloc bergan qiymatlar: 0 0 0 0 0 
malloccalloc
Argumentlarjami baytlarelementlar soni, hajmi
Boshlang'ich qiymataxlatnol
Tezliktezroqbiroz sekinroq

realloc - hajmni o'zgartirish #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int sigim = 3;
    int *massiv = malloc(sigim * sizeof(int));

    if (massiv == NULL) {
        return 1;
    }

    for (int i = 0; i < sigim; i++) {
        massiv[i] = i + 1;
    }

    /* joy yetmadi - kengaytiramiz */
    int yangi_sigim = 6;
    int *vaqtinchalik = realloc(massiv, yangi_sigim * sizeof(int));

    if (vaqtinchalik == NULL) {
        free(massiv);        /* eski xotira hali ham bizda */
        return 1;
    }

    massiv = vaqtinchalik;

    for (int i = sigim; i < yangi_sigim; i++) {
        massiv[i] = i + 1;
    }

    printf("Kengaytirilgan massiv: ");
    for (int i = 0; i < yangi_sigim; i++) {
        printf("%d ", massiv[i]);
    }
    printf("\n");

    free(massiv);
    return 0;
}
Natija
Kengaytirilgan massiv: 1 2 3 4 5 6 
realloc natijasini vaqtinchalik o'zgaruvchiga oling
C
massiv = realloc(massiv, yangi_hajm);    /* XAVFLI */

realloc muvaffaqiyatsiz bo'lsa NULL qaytaradi - va siz eski manzilni yo'qotasiz. Xotira ajratilgan holicha qoladi, lekin unga yetib bo'lmaydi. Bu klassik xotira sizishi.

To'g'ri yo'l - yuqoridagi kabi vaqtinchalik ko'rsatkich ishlatish.

O'sib boruvchi massiv #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int sigim = 2;
    int soni = 0;
    int *sonlar = malloc(sigim * sizeof(*sonlar));

    if (sonlar == NULL) {
        return 1;
    }

    for (int qiymat = 10; qiymat <= 100; qiymat += 10) {
        if (soni == sigim) {
            sigim *= 2;                 /* joyni ikki barobar oshiramiz */
            int *yangi = realloc(sonlar, sigim * sizeof(*sonlar));

            if (yangi == NULL) {
                free(sonlar);
                return 1;
            }

            sonlar = yangi;
            printf("Sig'im %d ga oshirildi\n", sigim);
        }

        sonlar[soni++] = qiymat;
    }

    printf("Natija (%d ta): ", soni);
    for (int i = 0; i < soni; i++) {
        printf("%d ", sonlar[i]);
    }
    printf("\n");

    free(sonlar);
    return 0;
}
Natija
Sig'im 4 ga oshirildi
Sig'im 8 ga oshirildi
Sig'im 16 ga oshirildi
Natija (10 ta): 10 20 30 40 50 60 70 80 90 100 
Nima uchun ikki barobar?

Har safar bitta joy qo'shsangiz, realloc har qo'shishda chaqiriladi va xotira ko'chiriladi - bu juda sekin.

Ikki barobar oshirish o'rtacha hisobda har bir qo'shish uchun doimiy vaqt beradi. Python'ning ro'yxati, C++ ning vector i ham xuddi shunday ishlaydi.

Foydalanuvchi bergan hajm #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int soni = 7;          /* amalda bu scanf orqali o'qiladi */

    double *ballar = malloc((size_t) soni * sizeof(*ballar));

    if (ballar == NULL) {
        fprintf(stderr, "Xotira yetmadi\n");
        return 1;
    }

    for (int i = 0; i < soni; i++) {
        ballar[i] = 60.0 + i * 5.5;
    }

    double yigindi = 0.0;
    for (int i = 0; i < soni; i++) {
        yigindi += ballar[i];
    }

    printf("%d ta ball uchun o'rtacha: %.2f\n", soni, yigindi / soni);

    free(ballar);
    return 0;
}
Natija
7 ta ball uchun o'rtacha: 76.50

Ikki o'lchamli dinamik massiv #

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int qatorlar = 3;
    int ustunlar = 4;

    /* qatorlarga ko'rsatkichlar massivi */
    int **jadval = malloc(qatorlar * sizeof(*jadval));

    if (jadval == NULL) {
        return 1;
    }

    for (int i = 0; i < qatorlar; i++) {
        jadval[i] = malloc(ustunlar * sizeof(**jadval));

        if (jadval[i] == NULL) {
            for (int j = 0; j < i; j++) {
                free(jadval[j]);        /* oldingilarini tozalaymiz */
            }
            free(jadval);
            return 1;
        }
    }

    int hisob = 1;
    for (int i = 0; i < qatorlar; i++) {
        for (int j = 0; j < ustunlar; j++) {
            jadval[i][j] = hisob++;
        }
    }

    for (int i = 0; i < qatorlar; i++) {
        for (int j = 0; j < ustunlar; j++) {
            printf("%4d", jadval[i][j]);
        }
        printf("\n");
    }

    /* teskari tartibda ozod qilamiz */
    for (int i = 0; i < qatorlar; i++) {
        free(jadval[i]);
    }
    free(jadval);

    return 0;
}
Natija
   1   2   3   4
   5   6   7   8
   9  10  11  12
Oddiyroq yo'l - bitta blok
C
int *jadval = malloc(qatorlar * ustunlar * sizeof(int));

/* [i][j] o'rniga: */
jadval[i * ustunlar + j] = qiymat;

free(jadval);          /* bitta free yetarli */

Bu usul:

  • tezroq - xotira ketma-ket yotadi, protsessor keshi yaxshi ishlaydi;
  • xavfsizroq - bitta malloc, bitta free;
  • soddaroq - qisman muvaffaqiyatsizlikni boshqarish shart emas.

Katta matritsalar bilan ishlaganda deyarli doim shu usul tanlanadi.

Xotira sizishi #

C
void sizadi(void) {
    int *massiv = malloc(1000 * sizeof(int));
    /* free chaqirilmadi - xotira yo'qoldi */
}
Uch xil xotira xatosi Xotira sizishi malloc bor, free yo'q Dastur asta-sekin xotirani yeb boradi Sekin o'ldiradi Ikki marta free Bir xotira ikki marta ozod qilinadi Uyum buziladi Darhol qulaydi Osilgan ko'rsatkich free dan keyin yana ishlatiladi Begona ma'lumot o'qiladi Eng xavflisi Yechim: har bir malloc uchun aynan bitta free, keyin NULL
Uchalasi ham C da eng ko'p uchraydigan jiddiy xatolar
Uchta halokatli xato

1. Ikki marta ozod qilish

C
free(massiv);
free(massiv);        /* uyum buziladi */

2. Ozod qilingandan keyin ishlatish

C
free(massiv);
massiv[0] = 42;      /* begona xotiraga yozish */

3. malloc qilinmagan xotirani ozod qilish

C
int massiv[10];
free(massiv);        /* stekdagi xotirani free qilib bo'lmaydi */

Barchasidan himoya - bitta oddiy odat:

C
free(massiv);
massiv = NULL;       /* endi tasodifan ishlatib bo'lmaydi */

free(NULL) xavfsiz - u hech narsa qilmaydi. Shuning uchun ikki marta free ham endi zararsiz.

Xotirani tekshirish vositalari #

Terminal
# Valgrind bilan (Linux / macOS)
gcc -std=c17 -g dastur.c -o dastur
valgrind --leak-check=full ./dastur
Natija
==12345== HEAP SUMMARY:
==12345==     in use at exit: 4,000 bytes in 1 blocks
==12345==   total heap usage: 1 allocs, 0 frees, 4,000 bytes allocated
==12345==
==12345== 4,000 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2FB0F: malloc
==12345==    by 0x108668: sizadi (dastur.c:5)
Terminal
# AddressSanitizer bilan (GCC va Clang da o'rnatilgan)
gcc -std=c17 -g -fsanitize=address,undefined dastur.c -o dastur
./dastur
-fsanitize=address ni odat qiling

Bu bayroq dasturni biroz sekinlashtiradi, lekin quyidagilarni darhol va aniq joyini ko'rsatib aniqlaydi:

  • massiv chegarasidan chiqish;
  • ozod qilingandan keyin ishlatish;
  • ikki marta free;
  • xotira sizishi.

Ishlab chiqish paytida uni doim yoqing. 19-bo'limda batafsil ko'ramiz.

Funksiyadan xotira qaytarish #

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *nusxa_yarat(const char *manba) {
    size_t uzunlik = strlen(manba);
    char *nusxa = malloc(uzunlik + 1);      /* +1 nol uchun */

    if (nusxa == NULL) {
        return NULL;
    }

    strcpy(nusxa, manba);
    return nusxa;                            /* chaqiruvchi free qiladi */
}

int main(void) {
    char *ism = nusxa_yarat("Husanboy Qodirov");

    if (ism == NULL) {
        return 1;
    }

    printf("Nusxa: %s\n", ism);
    ism[0] = 'X';
    printf("O'zgartirildi: %s\n", ism);

    free(ism);
    return 0;
}
Natija
Nusxa: Husanboy Qodirov
O'zgartirildi: Xusanboy Qodirov
Kim ozod qiladi?

Funksiya xotira qaytarsa, kim uni ozod qilishi kerakligini hujjatlashtirish shart:

C
/**
 * Satrning nusxasini uyumda yaratadi.
 * Chaqiruvchi natijani free() bilan ozod qilishi shart.
 * Xotira yetmasa NULL qaytaradi.
 */
char *nusxa_yarat(const char *manba);

Bu C dagi eng muhim kelishuvlardan biri. Uni buzsangiz - yo xotira sizadi, yo ikki marta free bo'ladi.

Qoidalar to'plami #

Dinamik xotira bilan ishlash qoidalari
#Qoida
1Har bir malloc uchun aynan bitta free
2malloc natijasini NULL ga tekshiring
3free dan keyin ko'rsatkichni NULL qiling
4realloc natijasini vaqtinchalik o'zgaruvchiga oling
5sizeof(*kursatkich) yozing, turni takrorlamang
6Xotirani kim ozod qilishini hujjatlang
7Ishlab chiqishda -fsanitize=address ishlating
8Iloji bo'lsa - stekdan foydalaning, u xavfsizroq

Oxirgi qoida muhim: agar hajm oldindan ma'lum va katta bo'lmasa, oddiy massiv ishlating. Dinamik xotira faqat haqiqatan kerak bo'lganda.

Amaliy topshiriq
  1. malloc bilan 10 elementli massiv yaratib, to'ldiring va ozod qiling.
  2. malloc natijasini tekshirmasdan ishlatib ko'ring.
  3. calloc va malloc bergan boshlang'ich qiymatlarni solishtiring.
  4. realloc bilan massivni kengaytiring.
  5. O'sib boruvchi massiv yozing (sig'im ikki barobar oshsin).
  6. free ni unutgan dastur yozib, valgrind bilan tekshiring.
  7. Ikki marta free chaqirib, nima bo'lishini ko'ring.
  8. free dan keyin ko'rsatkichni ishlatib ko'ring.
  9. Ikki o'lchamli dinamik massiv yarating va to'g'ri ozod qiling.
  10. Satr nusxasini qaytaruvchi funksiya yozing va uni free qiling.

Xulosa #

  • Stek - tez, avtomatik, kichik. Uyum - katta, moslashuvchan, qo'lda boshqariladi.
  • malloc(n) - n bayt so'raydi, boshlang'ich qiymati axlat.
  • calloc(n, hajm) - nol bilan to'ldirilgan xotira beradi.
  • realloc hajmni o'zgartiradi - natijani vaqtinchalik o'zgaruvchiga oling.
  • free xotirani qaytaradi; free(NULL) xavfsiz.
  • malloc natijasini doim NULL ga tekshiring.
  • free dan keyin ko'rsatkichni NULL qiling.
  • Uch halokatli xato: sizish, ikki marta free, osilgan ko'rsatkich.
  • sizeof(*kursatkich) yozing - turni takrorlamang.
  • Xotirani kim ozod qilishini hujjatlang.
  • Ishlab chiqishda -fsanitize=address ishlating.

Keyingi bo'limda strukturalarni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.