Derleme vaqtidagi o'lchamni bilmasdan kompilyator xotirani qanday ajratadi?

Foydalanuvchi tomonidan kiritilgan tamsaytli uskuna qabul qilgan C dasturini yozdim, u butun sonning kattaligi sifatida ishlatiladi va u qiymatdan foydalanib berilgan o'lchamdagi qatorni e'lon qiladi va men uni qator kattaligini tekshirish orqali tasdiqlayman.

Kod:

#include 
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%ld",sizeof(k));
    return 0;
}

va ajablanarli tomoni shundaki, u to'g'ri! Dastur kerakli o'lchamdagi qatorni yaratishi mumkin Biroq, barcha statik xotira ajratish kompilyatsiya vaqtida amalga oshiriladi va kompilyatsiya vaqtida n ning qiymati ma'lum emas, shuning uchun kompilyator kerakli o'lchamdagi xotirani qanday taqsimlay oladi?

Agar kerakli xotirani xuddi shu tarzda ajratsak, malloc() va calloc() yordamida dinamik ajratishni qanday ishlatish mumkin?

66
@o_weisman: bu C ++ emas, balki kompilyatorlar ushbu xususiyatni C11 ga mos ravishda qo'llab-quvvatlashi mumkin.
qo'shib qo'ydi muallif Sebastian Mach, manba
OPlar e'lon qilingan kod VLAs (Argumentlar uzunligi massivlari) dan foydalanadi. Bu kompilyatsiya vaqtini xotira ajratish emas, balki ish vaqti xotira ajratish emas. VLA xususiyati tilga so'nggi qo'shilgan.
qo'shib qo'ydi muallif user3629249, manba
@jamesqf: Bu new / delete dan juda yomonroq, lekin xotiradan ortiqcha ishlaydigan zamonaviy operatsion tizimlarga qaraganda bu juda yomon. Operatsion tizimda jismoniy RAM + almashtirish makoniga ega bo'lganingizdan ko'ra ko'proq RAM ajratishingiz mumkin va uning barchasi sizning jarayoningizni o'ldirishga qaror qilgan yadroga olib kelishi mumkin. (Linux bu OOM qotilini chaqiradi). linuxdevcenter. com/pub/a/linux/2006/11/30/& hellip; . Shu bilan siz malloc aslida NULLni qaytarib olasiz, lekin bu asl qiymati emas, balki jarayonni ajratishi mumkin bo'lgan virtual xotira miqdori chegaralarini belgilash orqali ajratish muvaffaqiyatsiz amalga oshiriladi.
qo'shib qo'ydi muallif Peter Cordes, manba
@jamesqf: Bu o'lchami tekshirilmaydi, va u bejirim tarzda bajarilmaydi. Dasturchi dastur ustida ishlashni umid qiladigan ilovalar uchun juda katta bo'lgan VLA-ni ishlatadigan dasturlarni yozmasligi kerak. (masalan, Linux x86-64 da yangi foydalanuvchi bo'sh joylarida 8MB hajmdagi hajm ). Umuman, siz xotiraga suyakning pastki qismidan pastga tegsangiz, segfault va operatsion tizim juda ko'p va sizning yig'ma xaritalashni rivojlantirmagani uchun qaror qiladi. Bolalarning barglari bo'lmagan funktsiyalarda VLA'larni ham ishlatishi mumkin bo'lgan katta VLA-ni ishlatish noto'g'ri.
qo'shib qo'ydi muallif Peter Cordes, manba
@jamesqf: ishlash. rsi (x86-64 SysV ABI-da printf uchun 2-chi arg tayyor bo'lishi mumkin) n bilan pastki rsp, rsi oddiy juda calloc funktsiyasidan ko'proq arzonroq). Bu holda k [] o'zi ishlatilmaydi, faqat sizeof (k) , shuning uchun yaxshi derleyici aslida printf -ni tanlang. Xotirasi L1D keshida va TLB-da zaifdir, shuning uchun kichik buferlar uchun yaxshi joy. Bundan tashqari, uni ozod qilish juda arzon va kompilyator siz uchun buni amalga oshirgani uchun uni noto'g'ri qabul qila olmaysiz.
qo'shib qo'ydi muallif Peter Cordes, manba
Bu to'g'ri emas. Qaysi derleyici foydalanayapsiz?
qo'shib qo'ydi muallif o_weisman, manba
k = (int *) calloc (n, sizeof (int)) int k [n]; kodi qanday qilib kodlangan? O'ylaymanki, avvalroq o'qiyotgan (agar siz VLAs mavjudligini bilsangiz).
qo'shib qo'ydi muallif Arcinde, manba
men gcc dan foydalanmoqdaman! Ubantuda.
qo'shib qo'ydi muallif Rahul, manba
@Peter Cordes: Kompilyator kerakli bo'shliq hajmini ishlash vaqti ma'lum bo'lmaganda, qanday qilib bo'shliqni saqlashni biladi? Ko'rinayaptiki, bu kichik arrayslarda ishlashi mumkin, ammo "kichik" degani yoki "kichkina" bo'lish uchun cheklov nima? Katta n uchun kam miqdorda suyuqlik bo'lsa, unda qanday qilib bu naqadar muvaffaqiyatsiz bo'ladi?
qo'shib qo'ydi muallif jamesqf, manba
@Arcinde: Tushkunlikka tushgan, chunki (ko'p narsalar kabi), menimcha, ko'pchilik tajribali dasturchilar bo'lmasa) a) mavjud bo'lmasligini bilaman; va b) agar ular buni ko'rsalar, aslida ishlay olmasligiga ishonishmaydi. Ishonchim komil emas.
qo'shib qo'ydi muallif jamesqf, manba
Nima uchun bu oddiy "k = (int *) calloc (n, sizeof (int)) o'rniga"? Sizning kodingizni siqish uchunmi?
qo'shib qo'ydi muallif jamesqf, manba

5 javoblar

Bu "statik xotira ajratish" emas. Sizning qator k o'zgaruvchan uzunlik qatori (VLA), ya'ni bu qator uchun xotira ish vaqtida ajratilgan degan ma'noni anglatadi. Hajmi n ning ish vaqti qiymati bilan aniqlanadi.

Tilning spetsifikatsiyasi biron bir maxsus ajratish mexanizmini belgilamaydi, lekin odatda dasturda k odatda oddiy int * ko'rsatgichi bo'lib qoladi va haqiqiy xotira bloklari ish vaqti davomida suyakka.

Agar VLA sizeof operatori ishlash vaqtida ham baholanadi, shuning uchun siz tajribangizda undan to'g'ri qiymatni olasiz. size_t turidagi qiymatlarni chop qilish uchun % zu (% ld emas, balki) dan foydalaning.

malloc (va boshqa dinamik xotira ajratish funktsiyalari) ning asosiy maqsadi - lokal ob'ektlarga taalluqli doiraga asoslangan hayotiy qoidalarni bekor qilishdir. Ie. malloc bilan ajratilgan xotira, "abadiy" ajratilgan bo'lib qoladi yoki uni free bilan aniq ajratib bo'lmaguningizcha. malloc bilan ajratilgan xotira blok oxirida avtomatik ravishda ajratilmaydi.

VLA, sizning namunangizdagidek, bu "qamrab oluvchi" funksiyani ta'minlamaydi. Sizning qator k muntazam tortib olsa bo'yicha umrbod ko'rsatmalariga bo'ysunadi: blokning oxirida uning umri tugaydi. Shuning uchun, odatda, VLA, malloc va boshqa dinamik xotira ajratish funktsiyalarini o'zgartira olmaydi.

Biroq, ma'lum vaqtlarda siz "mag'lubiyatni yo'qotish" kerak emas va faqat malloc dan foydalaning. VLA, albatta, malloc kodi>. Shuni yodda tutingki, VLA-lar odatda stackda joylashtirilgan va bu kungacha katta hajmdagi xotira ajratish juda shubhali dasturiy tajriba bo'lib qolmoqda.

72
qo'shib qo'ydi
@Rahul: C static VLAlarni qo'llab-quvvatlamaydi. Agar n ish vaqti qiymati bo'lsa, static int k [n] ruxsat berilmaydi. Lekin, agar ruxsat berilsa ham, har safar yangi xotira bloklarini ajratmaydi. Ayni paytda. malloc har bir qo'ng'iroq qilayotganingizda yangi blokni ajratadi. Shuning uchun static bilan bu erda malloc ga o'xshashlik yo'q.
qo'shib qo'ydi muallif AnT, manba
@Rahul: VLA C99 standartida joriy etildi. Rasmiy ravishda ular taxminan 18 yildan buyon ishlamoqda. Albatta, derleyici yordami biroz vaqt talab qildi.
qo'shib qo'ydi muallif AnT, manba
@Ilja Everilä: To'g'ri. Shunga qaramay, odatda, bir VLA dasturida, faqat marker sifatida amalga oshiriladi. Yo'q VLA bo'lmagan chiziqlar bu tarzda amalga oshirilmasa ham.
qo'shib qo'ydi muallif AnT, manba
@AnttiHaapala Shuning uchun men int * markerini chaqirmayman, balki uni internatsiz ravishda amalga oshirgan belgisi sifatida ko'rsataman. Savol, aslida, ichki mexanika haqida emas, balki til darajasidagi abstraktsiya haqida emas.
qo'shib qo'ydi muallif AnT, manba
@Rahul Yo'q, VLA ni funksiyadan qaytarib bo'lmaydi. Qanday?
qo'shib qo'ydi muallif AnT, manba
@rcgldr: VLA alloca ga juda o'xshaydi va albatta alloca tomonidan ilhomlantirilgan. Biroq, alloca xotirani "funktsiya" muddati bilan ajratadi: funktsiya chiqmaguncha xotira qoladi. Ie. u eng asosiysi - funktsiya tanasining o'zi tashqari barcha blok chegaralarini e'tiborsiz qoldiradi. Ayni paytda VLA normal bloklarga asoslangan umrga ega. Shu munosabat bilan VLA kodi alloca dan juda farq qiladi. Visual Studio, bugungi kunda VLA-ni qo'llab-quvvatlamaydi. Ammo, hech narsa qilinmaydi, chunki C11 VLA tili ixtiyoriy xususiyati.
qo'shib qo'ydi muallif AnT, manba
@rcgldr: Funktsiyaning boshida o'zgaruvchini e'lon qilish talabi 1970 yildan boshlab faqat standartning oldingi arxeik versiyalarida mavjud edi. C89/90da bunday cheklov yo'q. Men ushbu cheklovni o'rnatadigan har qanday C89/90 S kompilyatoridan xabardor emasman. Visual Studio hech qachon bu talabni (hech bo'lmaganda, "Visual Studio" deb nomlangan ekan), ya'ni Visual Studio 97 ning C kompilyatori C89/90 talablariga mos kelmas edi.
qo'shib qo'ydi muallif AnT, manba
alloca odatda kutubxona intrinsic darajasida qo'llanildi, tilning saralash qoidalariga aloqasi yo'q edi. Ehtimol, alloca ning yadro tilidagi ichki shaklini blok chegaralariga bo'ysundiradigan dasturni amalga oshirish imkoni bo'lishi mumkin edi, lekin bunday dasturlardan xabardor emasman. Endi bizda VLA mavjud bo'lib, asosan blok-kodli alloca
qo'shib qo'ydi muallif AnT, manba
@rcgldr: Birinchidan, nima uchun bu o'rinli ekanini tushunmayapman. Ikkinchidan, Visual Studio C99 uslubidagi o'zgarmaydigan deklaratsiyalarini bir necha versiyalarda C kodida qo'llab-quvvatladi. Shu vaqtdan boshlab Visual Studio da C kodidagi pastadiridagi uchun o'zgaruvchilarni e'lon qilishda muammo yo'q.
qo'shib qo'ydi muallif AnT, manba
«Odatda amalga oshirishda k sizning oddiy int * belgisi bo'lishi bilan yakunlanadi» jumlasi biroz xavfli ko'rinadi. Ko'rsatkichlar va massivlar haqida ko'plab chalkashliklar bor.
qo'shib qo'ydi muallif Ilja Everilä, manba
VLA asosan alloca() yoki _alloca() ga teng ekanligini unutmang. > odatda yig'imlardan ajratiladigan malloc() emas, balki, bu to'plamdan ajratib turadi. Bundan tashqari, Microsoft/Visual Studio kompilyatorlari VLA-ni qo'llab-quvvatlamaydi.
qo'shib qo'ydi muallif rcgldr, manba
@AnT - oldingi fikrlarimni o'chirib tashladi. Mening asosiy fikrimcha, VLA ning mallocga qaramasdan alloca kabi.
qo'shib qo'ydi muallif rcgldr, manba
Bu erda hech qanday "C" obyekti bo'lmaganligi uchun men, albatta, emas, bu kodini int * markerni chaqirmaslik kerak.
qo'shib qo'ydi muallif Antti Haapala, manba
static statik int k [n] dan foydalangan holda malloc() ga o'xshashmi?
qo'shib qo'ydi muallif Rahul, manba
@AnT: biz VLA funktsiyasini qaytara olamiz! malloc() kabi yaxshi bo'lishi mumkin!
qo'shib qo'ydi muallif Rahul, manba
O'sha paytda eski kompilyatordan foydalangan bo'lardim, baribir rahmat!
qo'shib qo'ydi muallif Rahul, manba
OK, rahmat, lekin 2 yil oldin ushbu dastur xato qilar edi! Shundan keyin biz buni qila olmasligimizni bilardim, lekin narsalar o'zgarib ketganday tuyuladi, shuning uchun nima uchun men uni aralashtirdim.
qo'shib qo'ydi muallif Rahul, manba

C da, derleyici VLA'ları (o'zgarmaydigan uzunluklu diziler) qo'llab-quvvatlaydigan vosita derleyici qadar - malloc() foydalanish shart emas va (va ko'pincha) ba'zan "stack" xotirasi - masalan standart kodning bir qismi bo'lmagan alloca() kabi sistemaga xos funktsiyalarni ishlatish. Agar istifdan foydalansangiz, qatorning maksimal kattaligi odatda malloc() <// code> deb nomlanadi, chunki zamonaviy operatsion tizimlar dasturlarga juda kichik hajmli xotira hajmiga ruxsat beradi.

11
qo'shib qo'ydi
Zamonaviy operatsion tizimlar (va hatto eski va o'rnatilgan operatsion tizimlarning ba'zi hollarda) foydalanuvchining stack hajmini sozlash imkonini beradi
qo'shib qo'ydi muallif M.M, manba

Argumentlar uzunligi massivlari uchun xotira aniq tarzda statik ajratilmagan. Biroq, bu to'plamga ajratilishi mumkin. Odatda, bu stack pointerda dinamik ravishda aniqlangan o'zgarishlarga qaramasdan, funktsiyalarning stack ramkalarining o'rnini kuzatish uchun "ramka pointer" ni qo'llashni o'z ichiga oladi.

Dasturingizni kompilyatsiya qilishga harakat qilsam, o'zgaruvchan qator uzunligidan optimallashtirilganligi ko'rinadi. Shunday qilib, men sizning kodni o'zgartirib, derivatni aslida qatorni ajratish uchun majbur qildim.

#include 
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    int k[n];
    printf("%s %ld",k,sizeof(k));
    return 0;
}

GUC 6.3 dan foydalangan holda qo'lni to'plash uchun Godbolt (qo'lni ASM ni o'qib olishim mumkin, chunki qo'lni qo'llagan holda) https://godbolt.org/g/5ZnHfa . (izohim)

main:
        push    {fp, lr}      ; Save fp and lr on the stack
        add     fp, sp, #4    ; Create a "frame pointer" so we know where
                              ; our stack frame is even after applying a 
                              ; dynamic offset to the stack pointer.
        sub     sp, sp, #8    ; allocate 8 bytes on the stack (8 rather
                              ; than 4 due to ABI alignment
                              ; requirements)
        sub     r1, fp, #8    ; load r1 with a pointer to n
        ldr     r0, .L3       ; load pointer to format string for scanf
                              ; into r0
        bl      scanf         ; call scanf (arguments in r0 and r1)
        ldr     r2, [fp, #-8] ; load r2 with value of n
        ldr     r0, .L3+4     ; load pointer to format string for printf
                              ; into r0
        lsl     r2, r2, #2    ; multiply n by 4
        add     r3, r2, #10   ; add 10 to n*4 (not sure why it used 10,
                              ; 7 would seem sufficient)
        bic     r3, r3, #7    ; and clear the low bits so it is a
                              ; multiple of 8 (stack alignment again) 
        sub     sp, sp, r3    ; actually allocate the dynamic array on
                              ; the stack
        mov     r1, sp        ; store a pointer to the dynamic size array
                              ; in r1
        bl      printf        ; call printf (arguments in r0, r1 and r2)
        mov     r0, #0        ; set r0 to 0
        sub     sp, fp, #4    ; use the frame pointer to restore the
                              ; stack pointer
        pop     {fp, lr}      ; restore fp and lr
        bx      lr            ; return to the caller (return value in r0)
.L3:
        .word   .LC0
        .word   .LC1
.LC0:
        .ascii  "%d\000"
.LC1:
        .ascii  "%s %ld\000"
10
qo'shib qo'ydi

Ushbu o'zgaruvchining uzunligi "VLA" deb nomlangan ushbu xotiraning xotirasi, xuddi shunga o'xshash tarzda alloca ga joylashtiriladi. To'g'ri, bu qanday amalga oshirilayotganligidan qat'i nazar siz foydalanadigan kompilyatorga bog'liq, biroq aslida bu ma'lum bo'lgan hajmni hisoblash va keyin [1] umumiy o'lchamni stack-pointerdan chiqarib yuborish holatidir.

Funksiyani tark etsangiz, bu ajratish "o'lsa", chunki malloc va do'stlaringizga kerak. [Va standart C ++ da amal qiladi]

[1] "nolga teng" deb ataladigan stackni ishlatadigan odatiy protsessorlar uchun.

3
qo'shib qo'ydi
@Davislor: C ++ mavhum mashinasida "stack" yo'q. Faqat avtomatik va dinamik va statsionar saqlash joylariga ega. Stakka ega bo'lish - bu faqat bitta dasturiy ta'minot, ammo u deyarli universaldir, shuning uchun bu javob to'g'ri. std :: vector , BTW uchun ishlashning o'rnini bosa olmaydi. GNU C ++ da VLA-larga ega, ammo afsuski, MSVC yo'q, shuning uchun katta kompilyatorlarda ishlaydigan x86 kodini optimallashtirishda faqatgina turi xavfsiz xotira uchun VLA foydalana olmaysiz. : /
qo'shib qo'ydi muallif Peter Cordes, manba
@Davislor: std :: get_temporary_buffer hech narsa emas gcc/clang ichida VLA yoki alloca kabi. new -ni ketma-ketroq sinashga harakat qiladilar: godbolt.org/g/VTfNzi standartning harfi va so'ralganidan kichikroq bo'lsa ham, biror narsalarni ajratishga harakat qilmoqda. std :: return_temporary_buffer bilan ozod bo'lishingiz kerak. (OTOH, agar derleyici ko'rsatgichning qochib ketmasligini isbotlay olsa, unda faqat bo'shliqni ajratib olish va return_temporary_buffer ni no-op qilish mumkin, ammo haqiqiy kompilyatorlar yo'q.)
qo'shib qo'ydi muallif Peter Cordes, manba
@Davislor: ha, men mavhumlikda C ++ stack yo'qligiga rozi bo'ldim. Va ha, nazariy jihatdan dinamik xotira bilan C VLAsni qo'llash mumkin. Ammo agar siz VJSni ozod qilish uchun longjmp bo'lsangiz, agar bu to'plamni grandparentda setjmp-ga qaytarib olishni istasangiz, unchalik ham bo'lmaydi. Lekin keyin men ishlash ixtiloflari tufayli amaliyotda yaxshi bo'lgan yaxshi amalga oshirishga rozilik bildirishga kirishdim. (Avkwark kalitini o'rta sharhni keyingi paytda yozganimda ko'rdim va orqaga qaytib, uni tuzatmadim.)
qo'shib qo'ydi muallif Peter Cordes, manba
@PeterCordes Biz mavzudan tashqariga chiqmoqchi bo'lsak-da, siz haqsiz: bu murakkablik. jmp_buf va longjmp ilovalarini yo'q qilish va ularni yo'q qilish uchun zarur bo'lgan har qanday VLAlar haqida bilish kerak, ammo boshqa C ++ ob'ektini avtomatik tarzda yo'q qilmaydi. VLAlarni amalga oshirishning maxsus maqsadi uchun alloca() deb nomlangan maxsus vektor kodi vector kodi> longjmp() . Har qanday holatda vector da C ++ da VLAga eng yaqin semantika mavjud.
qo'shib qo'ydi muallif Davislor, manba
@PeterCordes Fine, menimcha std :: get_temporary_buffer juda ko'p narsa alloca() kabi emas. :)
qo'shib qo'ydi muallif Davislor, manba
@PeterCordes Sizning birinchi javobingiz xuddi aytganimga qo'shilmasligingiz kabi yoziladi, lekin men nima amin emasman. Men shuningdek, standart "sizga biror bir narsani ishlatishini kafolatlamaydi" deb yozgan edim.
qo'shib qo'ydi muallif Davislor, manba
C ++ da std :: get_temporary_buffer ham mavjud.
qo'shib qo'ydi muallif Davislor, manba
C ++ da siz ishonganingizdek, siz o'zingizning ish stajida belgilashingiz mumkin bo'lgan va umr bo'yi joriy ko'lamda bo'lgan qator uchun std :: vector ni ishlatasiz. Bundan tashqari, std :: vector dan boshqa maqsadlar uchun foydalanishingiz mumkin. Shunday qilib, yaqin zaxira.
qo'shib qo'ydi muallif Davislor, manba
@lalala std :: vector siz foydalanadigan std :: allocator ni belgilash imkonini beradi. IIRC, garchi me'yorda siz hech qachon biror narsani ishlatishni kafolatlamaydi deb o'ylamayman. Lekin, agar alloca() deb chaqiradigan std :: vector bo'lsangiz, ulardan birini olishingiz mumkin. (Odatda, siz ularni qayta o'lchamasligingiz uchun emas, balki). Amalga kiritilgan dastur C ++ da C ish vaqtini yozishi va C VLAsni C ++ std :: vector bilan amalga oshirish mumkinligiga aminman. qopqoq!
qo'shib qo'ydi muallif Davislor, manba
longjmp ochiq-oydin emas VLA saqlash uchun talab qilinadi. Bunga C11 7.13.2.1 da yo'l qo'yiladi. Menimcha, bu kompilyatorlarning VLA'larni std :: vector yoki malloc / free , stack ajratish qiyin bo'lgan holatlarda.
qo'shib qo'ydi muallif Leushenko, manba
@Davislor, lekin bu stack ustida bo'lmaydi.
qo'shib qo'ydi muallif lalala, manba

Derleyici, vaqtni kompilyatsiya qilish parametrlari uchun xotirani ajratadigan deb aytilgan bo'lsa, bu o'zgaruvchining joylashtirilishi derleyici ishlab chiqaradigan emas, balki derivat ishlab chiqaradigan kodga biriktirilgan va o'rnatilgan ular uchun mavjud bo'lgan joy mavjud. Haqiqiy dinamik xotira ajratish, ishlab chiqilgan dastur tomonidan amalga oshiriladi.

0
qo'shib qo'ydi