Dasturlash

alloca stekdan xotira qanday ajratadi: __chkstk va stek probirovaniya mexanizmi

28-avgust, 2026, 20:121 ko'rish5 daqiqa o'qish
alloca stekdan xotira qanday ajratadi: __chkstk va stek probirovaniya mexanizmi

Ko'pgina dasturchilar stek (stack) xotirasini "tez va bepul" deb hisoblaydi: faqat ko'rsatgichni (stack pointer) surib, joy ajratib olasiz. Ammo bu soddalik yashirin xavfni bergib turadi — stekning oxirgi sahifasi (guard page) o'tib ketish. Agar funksiya bir marta katta hajmdagi xotira so'rasa va ko'rsatgich to'g'ridan-to'g'ri xavfsiz zonadan tashqariga sakrasa, tizim bu o'tishni sezmay qoldirishi mumkin va keyingi to'g'ri sahifaga yozish vaqti xatolikka yo'l qo'yadi.

Nima uchun stek probirovaniya kerak?

Operatsion tizimlar stek uchun ajratilgan virtual xotira blokining oxirida maxsus himoya sahifasi (guard page) belgilaydi. Bu sahifa oqilona huquqqa ega emas — unga kirish harakati protsessor tomonidan signalga aylantiriladi. Tizim bu signalni qabul qilib, stekni kengaytirishi (yangi fizikal sahifa biriktirishi) yoki dasturni to'xtatishi (stack overflow) mumkin.

#include <malloc.h>

void consume(void*,void*);

void f(int n)
{
    char buffer[16384];
    consume(alloca(n), buffer);
}

Agar funksiya kichik hajmdagi mahalliy o'zgaruvchilar e'lonsa, ko'rsatgich himoya sahifasidan oldin to'xtaydi — xavfsiz. Lekin katta massiv yoki alloca orqali dinamik ajratilganda ko'rsatgich himoya sahifasini "sakrab" o'tishi mumkin. Bu oldini olish uchun kompilyator (MSVC kabi) har bir katta ajratishdan oldin stekni probirovlaydi: himoya sahifasigacha bo'lgan yo'l ustidan o'tib, har bir sahifaga tegib o'tadi. Bu harakat sahifani "dirty" qiladi va OS uni fizikal xotiraga yuklashni majburlaydi.

alloca va oddiy massiv: bir xil miyoya, turli yo'llar

Raymond Chen blogida keltirilgan misolga ko'ra, alloca(n) va katta hajmdagi mahalliy massiv (char buffer[16384]) kompilyator tarafida deyarli identik qabul qilinadi. Ikkalasidan oldin ham __chkstk chaqiriladi.

Farq faqat shundaki: mahalliy massiv uchun hajm kompilyatsiya vaqti bilan aniq, alloca uchun esa run-time'da (ish paytida) belgilanadi. Bu demak, alloca uchun probirovaniya hajmi dinamik hisablanishi kerak.

Assembler darajasida nima sodir bo'ladi? (x86-64)

Quyidagi kod parchasi (MSVC natijasida) ikki marta __chkstk chaqirishining sababini ko'rsatadi:

push rbp
mov eax, 16416      ; 1. Mahalliy freym (buffer + qo'shimcha) uchun probirovaniya
eax qiymati __chkstk ga uzatiladi
call __chkstk
sub rsp, rax        ; Haqiqiylikda stek ko'rsatgichini surish

lea rbp, [rsp+32]   ; Freym ko'rsatgichini o'rnatish

movsxd rax, ecx     ; 2. alloca(n) uchun: n qiymatini rax ga yuklash
lea rcx, [rax+15]   ; 16 baytga tekislash (alignment)
and rcx, -16
mov rax, rcx        ; __chkstk chaqirish konvansiyasi: hajm rax da bo'lishi kerak
call __chkstk       ; Ikkinchi probirovaniya
sub rsp, rcx        ; alloca uchun joy ajratish
  • Birinchi chaqirish: Funksiya kirishida mahalliy freym (16384 bayt buffer + 32 bayt qo'shimcha = 16416) uchun probirovlanadi. eax registri hajmni o'tkazadi.
  • Ikkinchi chaqirish: alloca(n) uchun. ecx da kelgan n qiymati 16 baytga tekislanadi (x86-64 ABI talabi). __chkstk maxsus chaqirish konvansiyasiga ega: hajm rax da bo'lishi, natija ham rax da qaytariladi (probirowlangan hajm).

__chkstk chaqirish konvansiyasi va chetlantirish (alignment)

Diqqatga sazovor nuansa: __chkstk oddiy C chaqirish konvansiyasiga (Windows x64: RCX, RDX, R8, R9) rioya etmaydi. Uning o'ziga xos protokoli bor — bu kompilyator ichki funksiyasi (compiler intrinsic) ekanligi dalillati. Hajm RAX orqali o'tkaziladi.

        push    rbp
        mov     eax, 16416          ; probe for local frame
        call    __chkstk
        sub     rsp, rax            ; create local frame

        lea     rbp, [rsp+32]

        movsxd  rax, ecx            ; n
        lea     rcx, [rax+15]       ; round up to multiple of 16
        and     rcx, -16

        mov     rax, rcx            ; special __chkstk calling convention
        call    __chkstk
        sub     rsp, rcx            ; allocate n bytes

        lea     rdx, [rbp]          ; rdx -> buffer
        lea     rcx, [rsp+32]       ; rcx -> alloca'd memory
        call    consume

        lea     rsp, [rbp+16384]    ; clean up local frame
        pop     rbp
        ret     0

Chetlantirish (alignment) ham avtomatik amalga oshiriladi: lea rcx, [rax+15]; and rcx, -16. Bu n ni 16 baytlik chegaraga yuqtiradi. SIMD buyruqlar (AVX/SSE) va xotira kirish tezligi uchun bu majburiy.

Xavfsizlik va cheklovlar: nima qanday buzoq qilishi mumkin?

Ushbu mexanizim xavfsizlikni ta'minlasa-da, ba'zi cheklovlar va xavflar qoladi:

  • Standart emas: alloca (yoki MSVC da _alloca) C/C++ standartiga kirmaydi. Bu Microsoft, GCC, Clang kengaytmalari. Kod portativligi qanchalik muhim bo'lsa, malloc/free yoki std::vector afzal.
  • Iskushtiruvchi xato: Agar n juda katta bo'lsa (masalan, foydalanuvchi kiritimi tekshirilmagan holda), __chkstk himoya sahifasini topib, Stack Overflow istisnosini (Windows da STATUS_STACK_OVERFLOW) keltirib chiqaradi. Bu dastur to'xtashiga sabab bo'ladi, lekin bu xotira buzilishidan (corruption) ko'ra yaxshiroq.
  • Avtomatik tozalash: alloca bilan ajratilgan xotira funksiya tugagach avtomatik yo'qoladi (ko'rsatgich rbp asosida tiklanadi). Bu qulay, lekin longjmp yoki istisno (exception) paytida stek oqilona tiklanishi kafolatlanishi kerak (MSVC da bu ishlaydi).
  • Samaradorlik: Har bir alloca chaqirishida probirovaniya amalga oshiriladi. Tsikllar ichida katta hajmdagi alloca ishlatish — sekinlashishga yo'l qo'yadi. Bu yerda _malloca (xavfsizroq versiya) yoki tepada (heap) ajratish afzal.

Tizim dasturchisi nima bilishi kerak?

Bu mexanizimni tushunish tizim dasturlash (systems programming), kompilyator tuzish va byudjetli xotira menejmenti qilayotgan har bir muhandis uchun zarur:

  1. Stek — cheksiz emas. Uning chegarasi himoya sahifasi bilan belgilangan.
  2. Katta mahalliy o'zgaruvchilar va alloca kompyulyator tomonidan avtomatik probirovlanadi.
  3. __chkstk bu ishni bajaruvchi yashirin yordamchi — u sahifalarni birma-bir tegib o'tadi.
  4. Dinamik hajmli stek ajratishi (alloca) qulay, lekin xavfli: kiritma ma'lumotlarni tekshirish majburiy.

Keyingi safar char buf[20000] yozganda yoki alloca(size) chaqirganda, kompyulyator sizning o'riningizda stekni "tekshirib o'tganini" bilib qoling. Bu — xotira xavfsizligi uchun tushunmasdan amalga oshiriladigan eng murakkab va zaruri operatsiyalardan biri.

Asl manba: devblogs.microsoft.com

Manba: Hacker News
#stack #alloca #chkstk #memory-management #compiler-internals
Telegram da muhokama qilish