19-bo‘lim
Xotira xavfsizligi va nosozliklarni tuzatish
Aniqlanmagan xatti-harakat, eng ko'p uchraydigan xatolar, sanitizerlar, gdb va valgrind, xavfsiz kod yozish qoidalari.
Ushbu bo‘lim mundarijasi
- Aniqlanmagan xatti-harakat
- Sanitizerlar - eng foydali vosita
- Massiv chegarasidan chiqish
- Xotira sizishi
- Ozod qilingandan keyin ishlatish
- Valgrind
- gdb - nosozliklarni tuzatuvchi
- Kompilyator ogohlantirishlari
- Klassik xatolar to'plami
- 1. Bittaga adashish
- 2. = va ==
- 3. Nuqtali vergul
- 4. Satrga joy yetmasligi
- 5. printf format nomuvofiqligi
- 6. Mahalliy o'zgaruvchi manzilini qaytarish
- 7. Butun sonlarni bo'lish
- Xavfsizroq funksiyalar
- Xatolarni qaytarish uslubi
- Nosozlikni izlash usuli
- Xavfsiz kod qoidalari
- Xulosa
C sizni xatolardan himoya qilmaydi. Buning o'rniga u sizga to'liq nazorat beradi - va to'liq javobgarlikni ham.
Aniqlanmagan xatti-harakat #
| Xato | Misol |
|---|---|
| Massiv chegarasidan chiqish | m[10] (hajmi 5) |
NULL ko'rsatkichni ochish | *NULL |
| Ishga tushirilmagan o'zgaruvchi | int x; printf("%d", x); |
| Ozod qilingandan keyin ishlatish | free(p); *p = 1; |
Ikki marta free | free(p); free(p); |
| Butun son toshib ketishi | INT_MAX + 1 |
| Nolga bo'lish | a / 0 |
| Mahalliy o'zgaruvchi manzilini qaytarish | return &mahalliy; |
Satrda '\0' yo'q | printf("%s", nolsiz) |
%d bilan double chiqarish | printf("%d", 3.14) |
Kompilyator UB bo'lmasligini taxmin qiladi va shu asosda
optimallashtiradi. Shuning uchun -O2 bilan yig'ilgan dastur -O0 dan
butunlay boshqacha ishlashi mumkin.
Sanitizerlar - eng foydali vosita #
gcc -std=c17 -g -fsanitize=address,undefined dastur.c -o dastur
./dastur
Massiv chegarasidan chiqish #
#include <stdio.h>
int main(void) {
int ballar[5] = {85, 92, 78, 95, 88};
printf("%d\n", ballar[10]); /* chegaradan tashqarida */
return 0;
}
Oddiy kompilyatsiyada bu axlat son chiqaradi. Sanitizer bilan esa:
=================================================================
ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd...
READ of size 4 at 0x7ffd... thread T0
#0 0x... in main dastur.c:6
Address is located in stack of thread T0 at offset 48 in frame
#0 0x... in main dastur.c:3
This frame has 1 object(s):
[32, 52) 'ballar' (line 4) <== Memory access at offset 48 overflows
Xatoning aniq qatori ko'rsatildi.
Xotira sizishi #
#include <stdlib.h>
int main(void) {
int *massiv = malloc(100 * sizeof(int));
massiv[0] = 42;
return 0; /* free chaqirilmadi */
}
=================================================================
ERROR: LeakSanitizer: detected memory leaks
Direct leak of 400 byte(s) in 1 object(s) allocated from:
#0 0x... in malloc
#1 0x... in main dastur.c:4
Ozod qilingandan keyin ishlatish #
#include <stdlib.h>
int main(void) {
int *massiv = malloc(10 * sizeof(int));
free(massiv);
massiv[0] = 42; /* ozod qilingandan keyin */
return 0;
}
ERROR: AddressSanitizer: heap-use-after-free on address 0x...
WRITE of size 4 at 0x... thread T0
#0 0x... in main dastur.c:7
freed by thread T0 here:
#1 0x... in main dastur.c:6
# Ishlab chiqish uchun
gcc -std=c17 -Wall -Wextra -g -fsanitize=address,undefined dastur.c -o dastur
# Ishlab chiqarish uchun
gcc -std=c17 -Wall -Wextra -O2 dastur.c -o dastur
Sanitizer dasturni ~2 barobar sekinlashtiradi va ko'proq xotira talab qiladi - shuning uchun uni faqat ishlab chiqishda ishlating.
Lekin ishlab chiqishda uni doim yoqing. U topadigan xatolarning ko'pchiligini qo'lda topish deyarli imkonsiz.
Valgrind #
gcc -std=c17 -g dastur.c -o dastur
valgrind --leak-check=full --show-leak-kinds=all ./dastur
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 400 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2FB0F: malloc (vg_replace_malloc.c:299)
==12345== by 0x108668: main (dastur.c:4)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 400 bytes in 1 blocks
| Sanitizer | Valgrind | |
|---|---|---|
| Tezlik | ~2x sekin | ~20x sekin |
| O'rnatish | GCC/Clang ichida | alohida o'rnatiladi |
| Qayta kompilyatsiya | kerak | kerak emas |
| Aniqlik | juda yuqori | juda yuqori |
gdb - nosozliklarni tuzatuvchi #
gcc -std=c17 -g dastur.c -o dastur
gdb ./dastur
| Buyruq | Qisqartma | Nima qiladi |
|---|---|---|
run | r | Dasturni ishga tushiradi |
break 15 | b 15 | 15-qatorga to'xtash nuqtasi |
break main | b main | Funksiya boshida to'xtaydi |
next | n | Keyingi qator (funksiyaga kirmaydi) |
step | s | Keyingi qator (funksiyaga kiradi) |
continue | c | Keyingi to'xtash nuqtasigacha |
print x | p x | O'zgaruvchi qiymatini ko'rsatadi |
backtrace | bt | Chaqiruvlar zanjiri |
list | l | Kodni ko'rsatadi |
quit | q | Chiqish |
$ gdb ./dastur
(gdb) break 12
Breakpoint 1 at 0x1169: file dastur.c, line 12.
(gdb) run
Breakpoint 1, main () at dastur.c:12
12 int natija = bol(a, b);
(gdb) print a
$1 = 10
(gdb) print b
$2 = 0
(gdb) next
Program received signal SIGFPE, Arithmetic exception.
(gdb) backtrace
#0 bol (a=10, b=0) at dastur.c:5
#1 main () at dastur.c:12
backtrace - eng foydali buyruq. U dastur qayerda va qanday yo'l
bilan qulaganini ko'rsatadi.
ulimit -c unlimited # core fayllarni yoqamiz
./dastur # qulaydi
gdb ./dastur core
Yoki to'g'ridan-to'g'ri:
gdb --args ./dastur argument1 argument2
(gdb) run
Kompilyator ogohlantirishlari #
gcc -std=c17 -Wall -Wextra -Wpedantic -Wshadow -Wconversion dastur.c -o dastur
| Bayroq | Nimani topadi |
|---|---|
-Wall | Odatiy ogohlantirishlar |
-Wextra | Qo'shimcha ogohlantirishlar |
-Wpedantic | Standartdan chetlanishlar |
-Wshadow | Nomlar bir-birini yashirishi |
-Wconversion | Ma'lumot yo'qoladigan aylantirishlar |
-Werror | Ogohlantirishni xatoga aylantiradi |
-Werror ni jamoada ishlatinggcc -std=c17 -Wall -Wextra -Werror dastur.c -o dastur
Bu kod ogohlantirishsiz yig'ilishini majbur qiladi. Ogohlantirishlar to'planib qolsa, ular orasida haqiqiy xatoni ko'rmay qolasiz.
Qoida: ogohlantirishlar soni doim nol bo'lsin.
Klassik xatolar to'plami #
1. Bittaga adashish #
int massiv[5];
for (int i = 0; i <= 5; i++) { /* XATO: <= */
massiv[i] = 0; /* massiv[5] mavjud emas */
}
2. = va == #
if (holat = 1) { ... } /* qiymat berdi, taqqoslamadi */
3. Nuqtali vergul #
for (int i = 0; i < 10; i++); /* sikl bo'sh */
{
printf("%d\n", i); /* faqat bir marta */
}
4. Satrga joy yetmasligi #
char bufer[10];
strcpy(bufer, "Bu matn juda uzun"); /* bufer to'lib ketdi */
5. printf format nomuvofiqligi #
double narx = 99.5;
printf("%d\n", narx); /* XATO: %f bo'lishi kerak */
6. Mahalliy o'zgaruvchi manzilini qaytarish #
char *salomlash(void) {
char xabar[50] = "Salom";
return xabar; /* funksiya tugagach yo'q bo'ladi */
}
To'g'ri variantlar:
/* 1. Dinamik xotira */
char *salomlash(void) {
char *xabar = malloc(50);
if (xabar != NULL) strcpy(xabar, "Salom");
return xabar; /* chaqiruvchi free qiladi */
}
/* 2. Buferni chaqiruvchi bersin - eng yaxshi usul */
void salomlash(char *bufer, size_t hajm) {
snprintf(bufer, hajm, "Salom");
}
/* 3. static (ehtiyot bo'ling: bitta nusxa, ko'p oqimda xavfli) */
const char *salomlash(void) {
static char xabar[50] = "Salom";
return xabar;
}
7. Butun sonlarni bo'lish #
double ortacha = yigindi / soni; /* butun bo'lish! */
double togri = (double) yigindi / soni; /* to'g'ri */
Xavfsizroq funksiyalar #
| Xavfli | Xavfsizroq | Nima uchun |
|---|---|---|
gets | fgets | gets butunlay olib tashlangan |
strcpy | snprintf yoki strncpy + nol | Hajmni tekshiradi |
strcat | snprintf | Hajmni tekshiradi |
sprintf | snprintf | Hajmni tekshiradi |
atoi | strtol | Xatoni aniqlaydi |
scanf("%s") | fgets | Chegara qo'yadi |
#include <stdio.h>
int main(void) {
char toliq[20];
const char *ism = "Husanboy";
const char *familiya = "Qodirov";
int yozildi = snprintf(toliq, sizeof(toliq), "%s %s", ism, familiya);
printf("Natija : %s\n", toliq);
printf("Kerak edi : %d belgi\n", yozildi);
if (yozildi >= (int) sizeof(toliq)) {
printf("Ogohlantirish: matn kesildi!\n");
}
return 0;
}
Natija : Husanboy Qodirov
Kerak edi : 16 belgi
snprintf - eng foydali satr funksiyasisnprintf uchta muammoni bir yo'la hal qiladi:
- Hajmdan oshib ketmaydi;
- Nolni doim qo'yadi;
- Necha belgi kerak bo'lganini qaytaradi.
Qaytgan qiymat bufer hajmidan katta bo'lsa - matn kesilgan degani. Shuning uchun uni tekshirish kifoya:
if (snprintf(bufer, hajm, ...) >= (int) hajm) {
/* joy yetmadi */
}
Xatolarni qaytarish uslubi #
#include <stdio.h>
typedef enum {
XATO_YOQ = 0,
XATO_NOTOGRI_ARGUMENT,
XATO_NOLGA_BOLISH,
XATO_XOTIRA
} Xato;
const char *xato_matni(Xato x) {
switch (x) {
case XATO_YOQ: return "Xato yo'q";
case XATO_NOTOGRI_ARGUMENT: return "Noto'g'ri argument";
case XATO_NOLGA_BOLISH: return "Nolga bo'lish";
case XATO_XOTIRA: return "Xotira yetmadi";
default: return "Noma'lum xato";
}
}
Xato bol(int a, int b, int *natija) {
if (natija == NULL) {
return XATO_NOTOGRI_ARGUMENT;
}
if (b == 0) {
return XATO_NOLGA_BOLISH;
}
*natija = a / b;
return XATO_YOQ;
}
int main(void) {
int natija;
Xato x = bol(17, 5, &natija);
printf("17 / 5: %s", xato_matni(x));
if (x == XATO_YOQ) printf(" -> %d", natija);
printf("\n");
x = bol(17, 0, &natija);
printf("17 / 0: %s\n", xato_matni(x));
x = bol(17, 5, NULL);
printf("NULL : %s\n", xato_matni(x));
return 0;
}
17 / 5: Xato yo'q -> 3
17 / 0: Nolga bo'lish
NULL : Noto'g'ri argument
C da istisnolar (try/catch) yo'q. Uch keng tarqalgan uslub bor:
| Uslub | Misol | Qachon |
|---|---|---|
| Maxsus qiymat qaytarish | NULL, -1 | Oddiy funksiyalar |
| Xato kodi + natija ko'rsatkichda | Yuqoridagi misol | Aniqlik kerak bo'lsa |
Global errno | Standart kutubxona | Tizim chaqiruvlari |
Ikkinchi uslub eng aniq: qaysi xato ekani ma'lum bo'ladi va natija alohida qaytariladi.
Nosozlikni izlash usuli #
- Takrorlang. Xato qanday qilib chaqiriladi? Eng kichik misolni toping.
- Ogohlantirishlarni o'qing.
-Wall -Wextranima deyapti? - Sanitizerni yoqing. Ko'p xatolar shu bosqichda topiladi.
- Chop eting.
printfbilan qiymatlarni kuzating - eng oddiy va ko'pincha eng tez usul. - Yarmini kesing. Kodning yarmini o'chirib, xato qoladimi tekshiring. So'ng yana yarmini.
gdbni ishga soling.backtracevaprintbilan holatni ko'ring.- Boshqaga tushuntiring. Ko'pincha tushuntirish paytida xato o'z-o'zidan ko'rinadi.
Oxirgi usul «rezina o'rdak usuli» deb ataladi - va u haqiqatan ishlaydi.
Xavfsiz kod qoidalari #
| # | Qoida |
|---|---|
| 1 | O'zgaruvchini darhol ishga tushiring |
| 2 | Ko'rsatkichni NULL bilan boshlang |
| 3 | Ko'rsatkichni ishlatishdan oldin tekshiring |
| 4 | malloc natijasini tekshiring |
| 5 | free dan keyin NULL qiling |
| 6 | Massiv chegarasini doim kuzating |
| 7 | sizeof dan foydalaning, raqam yozmang |
| 8 | O'zgartirmasangiz const yozing |
| 9 | Xavfsizroq funksiyalarni tanlang (snprintf) |
| 10 | Ogohlantirishlar nol bo'lsin |
| 11 | Ishlab chiqishda sanitizer yoqing |
| 12 | Har bir fopen uchun fclose |
- Massiv chegarasidan chiqadigan dastur yozing va sanitizer bilan ishga tushiring.
freeni unutgan dastur yozing va sizishni aniqlang.freedan keyin xotirani ishlatib, sanitizer xabarini o'qing.NULLko'rsatkichni ochib,gdbdabacktraceqiling.-Wall -Wextra -Wconversionbilan eski kodingizni qayta yig'ing.- Nolga bo'lishni
gdbda kuzating. strcpyo'rnigasnprintfishlatib, kesilishni aniqlang.- Xato kodlari uchun
enumyarating va funksiyada ishlating. - Mahalliy o'zgaruvchi manzilini qaytaruvchi funksiya yozing va uni to'g'rilang.
- O'z kodingizni yuqoridagi 12 qoida bo'yicha tekshiring.
Xulosa #
- Aniqlanmagan xatti-harakat - dastur «ishlayotgandek» ko'rinishi mumkin, lekin natija ishonchsiz.
-fsanitize=address,undefined- eng foydali vosita; ishlab chiqishda doim yoqing.- Valgrind qayta kompilyatsiyasiz ishlaydi, lekin sekinroq.
gdbda eng muhim buyruq -backtrace.- Ogohlantirishlar soni doim nol bo'lsin; jamoada
-Werror. - Klassik xatolar: bittaga adashish,
=va==, ortiqcha nuqtali vergul, bufer to'lishi, format nomuvofiqligi. - Mahalliy o'zgaruvchi manzilini qaytarmang.
strcpyvasprintfo'rnigasnprintf.- C da istisnolar yo'q - xato kodi qaytaring.
- Xatoni izlashda tizimli boring: takrorlash → ogohlantirish →
sanitizer → chop etish →
gdb.
Keyingi bo'limda barcha bilimlarni birlashtirib, to'liq amaliy loyiha yaratamiz.
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.