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.
Ushbu bo‘lim mundarijasi
Shu paytgacha massiv hajmini oldindan yozib keldik. Ammo hajm faqat dastur ishlayotganda ma'lum bo'lsa-chi?
Stek va uyum #
malloc - xotira so'rash #
#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;
}
massiv[0] = 10
massiv[1] = 20
massiv[2] = 30
massiv[3] = 40
massiv[4] = 50
malloc ni to'g'ri yozishint *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:
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 tekshiringint *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 #
#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;
}
calloc bergan qiymatlar: 0 0 0 0 0
malloc | calloc | |
|---|---|---|
| Argumentlar | jami baytlar | elementlar soni, hajmi |
| Boshlang'ich qiymat | axlat | nol |
| Tezlik | tezroq | biroz sekinroq |
realloc - hajmni o'zgartirish #
#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;
}
Kengaytirilgan massiv: 1 2 3 4 5 6
realloc natijasini vaqtinchalik o'zgaruvchiga olingmassiv = 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 #
#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;
}
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
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 #
#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;
}
7 ta ball uchun o'rtacha: 76.50
Ikki o'lchamli dinamik massiv #
#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;
}
1 2 3 4
5 6 7 8
9 10 11 12
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, bittafree; - soddaroq - qisman muvaffaqiyatsizlikni boshqarish shart emas.
Katta matritsalar bilan ishlaganda deyarli doim shu usul tanlanadi.
Xotira sizishi #
void sizadi(void) {
int *massiv = malloc(1000 * sizeof(int));
/* free chaqirilmadi - xotira yo'qoldi */
}
1. Ikki marta ozod qilish
free(massiv);
free(massiv); /* uyum buziladi */
2. Ozod qilingandan keyin ishlatish
free(massiv);
massiv[0] = 42; /* begona xotiraga yozish */
3. malloc qilinmagan xotirani ozod qilish
int massiv[10];
free(massiv); /* stekdagi xotirani free qilib bo'lmaydi */
Barchasidan himoya - bitta oddiy odat:
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 #
# Valgrind bilan (Linux / macOS)
gcc -std=c17 -g dastur.c -o dastur
valgrind --leak-check=full ./dastur
==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)
# AddressSanitizer bilan (GCC va Clang da o'rnatilgan)
gcc -std=c17 -g -fsanitize=address,undefined dastur.c -o dastur
./dastur
-fsanitize=address ni odat qilingBu 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 #
#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;
}
Nusxa: Husanboy Qodirov
O'zgartirildi: Xusanboy Qodirov
Funksiya xotira qaytarsa, kim uni ozod qilishi kerakligini hujjatlashtirish shart:
/**
* 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 #
| # | Qoida |
|---|---|
| 1 | Har bir malloc uchun aynan bitta free |
| 2 | malloc natijasini NULL ga tekshiring |
| 3 | free dan keyin ko'rsatkichni NULL qiling |
| 4 | realloc natijasini vaqtinchalik o'zgaruvchiga oling |
| 5 | sizeof(*kursatkich) yozing, turni takrorlamang |
| 6 | Xotirani kim ozod qilishini hujjatlang |
| 7 | Ishlab chiqishda -fsanitize=address ishlating |
| 8 | Iloji 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.
mallocbilan 10 elementli massiv yaratib, to'ldiring va ozod qiling.mallocnatijasini tekshirmasdan ishlatib ko'ring.callocvamallocbergan boshlang'ich qiymatlarni solishtiring.reallocbilan massivni kengaytiring.- O'sib boruvchi massiv yozing (sig'im ikki barobar oshsin).
freeni unutgan dastur yozib, valgrind bilan tekshiring.- Ikki marta
freechaqirib, nima bo'lishini ko'ring. freedan keyin ko'rsatkichni ishlatib ko'ring.- Ikki o'lchamli dinamik massiv yarating va to'g'ri ozod qiling.
- Satr nusxasini qaytaruvchi funksiya yozing va uni
freeqiling.
Xulosa #
- Stek - tez, avtomatik, kichik. Uyum - katta, moslashuvchan, qo'lda boshqariladi.
malloc(n)-nbayt so'raydi, boshlang'ich qiymati axlat.calloc(n, hajm)- nol bilan to'ldirilgan xotira beradi.reallochajmni o'zgartiradi - natijani vaqtinchalik o'zgaruvchiga oling.freexotirani qaytaradi;free(NULL)xavfsiz.mallocnatijasini doimNULLga tekshiring.freedan keyin ko'rsatkichniNULLqiling.- Uch halokatli xato: sizish, ikki marta free, osilgan ko'rsatkich.
sizeof(*kursatkich)yozing - turni takrorlamang.- Xotirani kim ozod qilishini hujjatlang.
- Ishlab chiqishda
-fsanitize=addressishlating.
Keyingi bo'limda strukturalarni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.