Ծրագրավորման լեզուներ

C++ կոդի թարգմանումը դեպի ասեմբլերական կոդ

Araks Tigranyan
Picsart Academy
Published in
11 min readMar 30, 2021

--

Ինչպես ասել է ծրագրավորող Էրիկ Նագումը՝ «Կյանքը բավականաչափ երկար է C++ սովորելու համար»։

C++-ը օբյեկտ կողմնորոշված ծրագրավորման լեզու է, այն իրենից ներկայացնում է բարձր և ցածր մակարդակի լեզուների համադրություն, կարելի է ասել՝ հանդիսանում է միջին մակարդակի լեզու։

C++-ի պատմությունը սկիզբ է առել 1979 թվականից, երբ Bjarne Stroustrup-ն աշխատում էր իր ասպիրանտուրայի աշխատանքի վրա։ Լեզուներից մեկը, որի հետ Stroustrup-ը հնարավորություն ուներ աշխատելու Simula-ն էր, որը ինչպես անվանումն է ենթադրում` հիմնականում նախատեսված էր մոդելավորման համար։ Simula 67 լեզուն (լեզվի այն տարբերակը, որի հետ աշխատում էր Stroustrup-ը) դիտվում է որպես առաջին ծրագրավորման լեզու, որը հարում էր օբյեկտ կողմնորոշված ծրագրավորման գաղափարին։ Stroustrup-ը գտնում էր, որ այդ գաղափարը բավականին օգտակար է ծրագրային ապահովման համար, սակայն Simula լեզուն չափազանց դանդաղ էր գործնականում օգտագործման համար:

Դրանից կարճ ժամանակ անց, նա սկսում է աշխատել «C with Classes»-ի վրա, որը ինչպես անվանումից կարող ենք գլխի ընկնել, հիմնված էր լինելու C լեզվի գերակայության վրա։ Նրա նպատակն էր ավելացնել օբյեկտ կողմնորոշված ծրագրավորումը C լեզվին, որը իր դյուրատարության համար բավականին արժևորված լեզու էր և մինչ օրս էլ շարունակում է այդպիսինը համարվել, քանի որ չէր ազդում արագության և ցածր մակարդակի ֆունկցիոնալության վրա։ Stroustrup-ի լեզուն պարունակում էր դասեր, հիմնական ժառանգություն, նշումներ, տիպի ստուգում՝ ի լրումն C լեզվի բոլոր հատկությունների: Առաջին «C with Classes»-ի կոմպիլյատորը կոչվում էր Cfront, որը բխում էր C-ի կոմպիլյատորից՝ CPre-ից։ Դա ծրագիր էր, որը նախագծված էր թարգմանելու «C with Classes»-ը սովորական C-ի։ Մեկ այլ հիշարժան փաստ է այն, որ Cfront-ը հիմնականում գրված էր C with Classes-ով՝ դարձնելով այն ինքնասպասարկող կոմպիլյատոր։

Ավելի ուշ՝ 1993թ-ին, Cfront- ը դադարեց կիրառվել այն բանից հետո, երբ դժվարացավ դրանում ներառել նոր առանձնահատկություններ, մասնավորապես C++ բացառությունները: Չնայած դրան, Cfront-ը մեծ ազդեցություն ունեցավ ապագա կոմպիլյատորների և Unix օպերացիոն համակարգի վրա:

1983թ-ին լեզվի անունը «C with Classes»-ից փոխվեց և դարձավ C++: Այստեղ ++ -ը C լեզվում փոփոխականը 1-ով ավելացնելու օպերատոր է, որը պատկերացում է տալիս, թե ինչպես էր Stroustrup-ը վերաբերվում լեզվին։ Այս ընթացքում ավելացվեցին մի շարք նոր առանձնահատկություններ, որոնցից առավել ուշագրավները` վիրտուալ ֆունկցիաները, ֆունկցիայի գերբեռնվածությունը, & նշանով հղումները, const հիմնաբառը և մեկ տողով մեկնաբանություններն էին։

1985թ-ին Stroustrup-ը տպագրեց «The C++ Programming Language» գիքրը։ Նույն տարում C++-ը ներկայացվեց` որպես առևտրային արտադրանք։ Լեզուն դեռևս պաշտոնապես ստանդարտացված չէր, բայց գրքի հրատարակումը հիմք հանդիսացավ հետագայում նաև դրան հասնելու համար։ Լեզուն կրկին թարմացվեց 1989թ -ին ՝ ներառելով պաշտպանված և ստատիկ անդամներ, ինչպես նաև ժառանգություն մի քանի դասերից:

1990 թ-ին լույս է տեսնում «The Annotated C++ Reference Manual»-ը: Նույն թվականին Borland֊ի Turbo C++ կոմպիլյատորը թողարկվում է որպես առևտրային արտադրանք: Turbo C++ -ը ավելացրեց լրացուցիչ գրադարանների մեծ քանակություն, որոնք զգալի ազդեցություն ունեցան C++-ի զարգացման վրա: Չնայած Turbo C++ -ի վերջին կայուն թողարկումը 2006-ին էր, այնուամենայնիվ, կոմպիլյատորը դեռևս լայնորեն օգտագործվում է:

1998 թվականին C++ ստանդարտների կոմիտեն հրապարակեց առաջին միջազգային ստանդարտը՝ C++ ISO / IEC 14882: 1998-ը, որը ոչ ֆորմալ կերպով հայտնի դարձավ որպես C++ 98: Ասում էին, որ ծանոթագրված C++ տեղեկատու ձեռնարկը (The Annotated C++ Reference Manual) մեծ ազդեցություն ունի ստանդարտի մշակման վրա: Ստանդարտ ձևանմուշների գրադարանը (The Standard Template Library) նույնպես ներառված էր ստանդարտում։ Արդեն 2003 թ-ին կոմիտեն արձագանքեց բազմաթիվ խնդիրների, որոնք հաղորդել էին իրենց 1998 թ -ի ստանդարտի համաձայն և դրանց հիման վրա վերանայեց այն: Փոփոխված լեզուն ստացավ C++ 03 անվանումը:

2005 թ-ին C++ ստանդարտների կոմիտեն թողարկեց տեխնիկական զեկույց (TR1 անվամբ), որտեղ մանրամասն նկարագրվում էին տարբեր առանձնահատկություններ, որոնք նրանք պատրաստվում էին ավելացնել C++ վերջին ստանդարտին: Նոր ստանդարտը ոչ ֆորմալ անվանեցին C++ 0x, քանի որ ակնկալվում էր, որ այն կթողարկվի մինչ առաջին տասնամյակի ավարտը: Սակայն նոր ստանդարտը թողարկվեց 2011թ -ի կեսերից ոչ շուտ ։

2011-ի կեսերին ավարտեցին նոր C++ ստանդարտը։ Boost գրադարանի նախագիծը զգալի ազդեցություն ունեցավ նոր ստանդարտի վրա և որոշ նոր մոդուլներ ստեղծվեցին անմիջապես համապատասխան Boost գրադարաններից: Նոր առանձնահատկություններից մի քանիսը ներառում էին կանոնավոր արտահայտման աջակցություն, պատահականացման համապարփակ գրադարան, նոր C++ ժամանակի գրադարան, ատոմիկայի աջակցություն, ստանդարտ ճյուղավորված (threading) գրադարան, for ցիկլի շարահյուսորեն նոր տարբերակ և այլն։

Bjarne Stroustrup-ը հիանալի աշխատանք է կատարել C++ -ի հետ։ C- ն կարող է լինել ցածր մակարդակի ծրագրավորման լեզու, քանի որ այն չունի դասեր, չի պարունակում մի քանի տարբերակ, որոնք ավելի լավ ծրագրավորում կստեղծեն։ Stroustrup-ը C-ին ավելացրեց այն, որ սկսեց կոդին վերաբերվել կյանքում գոյություն ունեցող օբյեկտների պես։ C++- ի գրավչությունը վերջինիս բարձր մակարդակի ծրագրավորման լեզու լինելն ու C-ի արագությունն ունենալն է, ուստի չենք վախենա ասել, որ C++ -ը թերևս ամենաարդյունավետն է բոլորից:

Քանի որ պատրաստվում ենք ուսումնասիրել C++ կոդի թարգմանումը ասեմբլերական լեզվի, անհրաժեշտ է խոսել նաև ասեմբլերի էության մասին` հասկանալու համար, թե ինչ է այն իրենից ներկայացնում։
Յուրաքանչյուր համակարգիչ ունի միկրոպրոցեսոր, որը ղեկավարում է համակարգչի թվաբանական, տրամաբանական և կառավարման գործողությունները:
Պրոցեսորների յուրաքանչյուր ընտանիք ունի իր ինստրուկցիաները` տարբեր գործողությունների մշակման համար, ինչպես օրինակ՝ ստեղնաշարից մուտքային տվյալներ ստանալը, էկրանին տեղեկատվություն ցուցադրելը և տարբեր այլ աշխատանքներ կատարելը: Հրահանգների այս շարքը կոչվում է «մեքենայական լեզվի հրահանգներ»:

Պրոցեսորը հասկանում է միայն մեքենայական լեզվի հրահանգները, որոնք էլ 1-ի և 0-ի տողեր են: Այնուամենայնիվ, մեքենայական լեզուն չափազանց անհասկանալի ու բարդ է` ծրագրակազմի մշակման մեջ օգտագործելու համար: Այսպիսով, ցածր մակարդակի ասեմբլեր լեզուն՝ նախատեսված պրոցեսորների որոշակի ընտանիքի համար, ներկայացնում է տարբեր հրահանգներ խորհրդանշական կոդով և ավելի հասկանալի ձևով:

Ասեմբլերը կամ ասեմբլերական լեզուները ցածր մակարդակի ծրագրավորման լեզուներ են, որոնք նախատեսված են համակարգչի կամ ցանկացած այլ ծրագրավորող սարքի համար: Նման լեզուները կրճատվում են որպես asm ու սովորաբար՝ ճարտարապետության մեքենայական կոդերի հրահանգների և այդ լեզուների միջև շատ սերտ կապ կա:

Յուրաքանչյուր ասեմբլեր լեզու համապատասխանում է միայն մեկ համակարգչի, այսինքն`ասեմբլերական լեզուների դեպքում առկա է առանձնահատկության բարձր աստիճան: Սա ստիպում է ասեմբլերական լեզուներին տարբերվել բարձր մակարդակի լեզուների մեծ մասից, քանի որ ասեմբլերական լեզուները չեն կարող օգտագործվել տարբեր համակարգիչների վրա, մինչդեռ բարձր մակարդակի լեզուները հիմնականում դյուրակիր են: Ասեմբլերական լեզուները հաճախ անվանում են «մեքենայական խորհրդանշական կոդեր»:

1955թ -ից հետո, երբ Stan Poley-ը գրեց Սիմվոլիկ օպտիմալ ասեմբլերական լեզուն IBM 650 համակարգչի համար, ասեմբլերական լեզուները սկսեցին լայնորեն օգտագործվել, քանի որ դրանք ծրագրավորողներին ազատում էին հոգնեցուցիչ առաջադրանքներից, ինչպիսին էր թվային կոդերը հիշելը: Դրանց օգտագործումը, սակայն, էապես կրճատվեց 1980-ականներին՝ բարձր մակարդակի լեզուների ի հայտ գալով:

Պատմության ընթացքում շատ ծրագրեր գրվել են ամբողջությամբ ասեմբլերական լեզվով: Այս միտումը փոխվեց 1961 թ-ին Browser MSP- ի ներդրմամբ, որը գրված էր ESPOL- ով։ Ի հավելումն կարելի է նշել, որ շատ առևտրային ծրագրեր գրվել են ասեմբլերական լեզուների միջոցով, այդպիսի օրինակ են IBM-ի հիմնական համակարգչային ծրագրերը:

Նախնական փուլում միկրոհամակարգիչները կախված էին հիմնականում ձեռքով ծածկագրված ասեմբլերական լեզուներից: Դա պայմանավորված էր բարձր մակարդակի լեզուներ ստեղծողների բացակայությամբ։ Բարձր մակարդակի լեզուները նախատեսված էին միկրոհամակարգիչներում օգտագործվելու համար: Ասեմբլերական լեզուները օժտված էին մի շարք առավելություններով, ինչպիսիք են նվազագույն չափը, բարձր արագությունը և բարձր հուսալիությունը:

1980–90-ականների ժամանակաշրջանի համակարգիչների մեծ մասը ստեղծվել են հիմնականում ասեմբլերական լեզուների օգտագործմամբ: Որպես օրինակներ կարելի է նշել Atari ST և MSX համակարգերը:

Իսկ ի՞նչ կապ կարող են ունենալ C++-ը և ասեմբլերական լեզուն։ Բանն այն է, որ C++֊ով գրված կոդը, մինչև մեքենայական կոդի վերածվելը (1֊երի և 0֊ների)՝ թարգմանվում է ասեմբլերական լեզվի, քանի որ ասեմբլերական լեզվից մեքենայական կոդ ստանալը շատ ավելի հեշտ է։

Ասեմբլերական լեզուն աշխատում է CPU-ի ռեգիստրների հետ, ինչից էլ կարելի է ենթադրել, որ ասեմբլերական լեզվով գրված կոդում կարելի է տեսնել ռեգիստրների անվանումներ։

Ասեմբլերական լեզուն կարող ենք բաժանել 3 մասի․

  • Տվյալների բաժին (data section)
  • bss բաժին (bss section)
  • Կոդի բաժին (text section)

Data Section

Տվյալների բաժինը օգտագործվում է նախնական տվյալների կամ հաստատունների հայտարարման համար: Այս տվյալները չեն փոխվում գործառույթի ժամանակ: Նշված բաժնում կարելի է հայտարարել տարբեր հաստատուն արժեքներ, ֆայլերի անուններ և այլն: Տվյալների բաժնի հայտարարման շարահյուսությունը հետևյալն է.

section.data

Bss Section

Bss բաժինը օգտագործվում է փոփոխականների հայտարարման համար: Bss բաժնի հայտարարման համար շարահյուսությունն է`

section.bss

Text Section

Տեքստային բաժինը օգտագործվում է փաստացի կոդը պահելու համար: Այս բաժինը պետք է սկսվի global _start հայտարարումով, որը միջուկին ասում է, թե որտեղ է սկսվում ծրագրի կատարումը: Տեքստային բաժնի հայտարարման շարահյուսությունը հետևյալն է`

section.text
global _start
_start:

Ասեմբլերական լեզուն օգտագործում է նաև մի շարք ինստրուկցիաներ, ինչպիսիք են mov, mul, add, jmp, jg և այլն։ Փորձենք ծանոթանալ ասեմբլերական ինստրուկցիաներին օրինակների միջոցով։ Առաջին ամենապարզ օրինակը էկրանին Hello, World! արտահայտությունը տպելն է։

C++ code

assembly code

Հետաքրքիր է նաև դիտարկել ասեմբլերական լեզվում թվերի և թվաբանական պարզագույն գործողությունների արտահայտումը։ Եթե հայտարարենք երկու փոփոխականներ և կատարենք դրանցով գումարում գործողությունը, C++-ում կստանանք հետևյալ կոդը․

Ասեմբլերական լեզվում վերևում ցուցադրված կոդը կարելի է պատկերացնել հետևյալ կերպով․

Այս կոդում տեղի է ունենում հետևյալը` նախևառաջ 6 արժեքը տեղադրվում է ռեգիստրներից մեկի մեջ և պահվում այնտեղ հետագա գործողության համար։ Արժեքը ռեգիստրում տեղադրվում է mov ինստրուկցիայի օգնությամբ։ Քանի որ գործողությունը կատարելու համար մեզ անհրաժեշտ է նաև երկրորդ արժեքը, այն նույնպես պահվում է ռեգիստրներից մեկում, այս պարագայում` ռեգիստր ebx-ում։ Ասեմբլերական լեզվում գումարում կատարելու համար օգտագործվում է add ինստրուկցիան, որի միջոցով գումարվում են eax և ebx ռեգիստրներում պահված արժեքները։ Հետաքրքիրն այն է, որ գումարման արդյունքը պահվում է eax ռեգիստրում։ Մյուս թվաբանական գործողությունները կատարելու համար կան այլ ինստրուկցիաներ, ինչպիսիք են օրինակ՝ sub (հանում), div (բաժանում), mul (բազմապատկում) և այլն։

Ցիկլի աշխատանքը ասեմբլերական լեզվում նույնպես արժանի է հիշատակման։ Պատկերացնենք մի խնդիր, որը մեզանից պահանջում է էկրանին տպել 1-ից 10 թվերը։ C++ լեզվում խնդիրը կարելի է ներկայացնել 2 եղանակով` for և while ցիկլերի միջոցով։

Ասեմբլերական լեզվում կոդը կարող է ունենալ հետևյալ տեսքը`

Այս կոդում տեսնում ենք մի քանի նոր ինստրուկցիաներ։ Վերը նշվածում կարող ենք տեսնել նաև այս աշխատանքում կիրառված առաջին պիտակը (labelREPEAT։ Այն ինստրուկցիա չէ և այս պարագայում REPEAT-ի հետ ոչինչ չի կատարվում։ Կարելի է ասել, որ այս և ընդհանրապես բոլոր label-ները այն կետերն են, որոնք օգնում են մեզ կոդում կատարել անցումներ՝ մեկ label-ից` մյուսը, կամ ուղղակի կոդի որևէ հատվածից թռիչք կատարել դեպի մեր ցանկացած label։

Print-ը ինստրուկցիա է և տվյալ դեպքում տպում է այն թիվը, որը գտնվում է eax ռեգիստրի մեջ։ Առաջին ցիկլի ժամանակ թիվը 1 է։ inc ինստրուկցիան բացվում է, որպես increment, ինչը թարգմանաբար նշանակում է արժեքը մեծացնել։ Տվյալ պարագայում արժեքը մեծանում է 1-ով։ Հաջորդը cmp ինստրուկցիան է, որը բացվում է որպես compare (համեմատել)։ Այն համեմատում է eax-ում եղած արժեքը (այս դեպքում արդեն 2-ը, քանի որ արժեքը մեկով մեծացել է) 10-ի հետ և անցնում կոդի հաջորդ տողին։ Վերջին ինստրուկցիան jle-ն է, այն բացվում է որպես jump less or equal (ցատկել փոքր կամ հավասար լինելու դեպքում)։ Ինստրուկցիայի կողքին գրված է REPEAT label-ը, որտեղ էլ թռիչք է կատարվում պայմանը չբավարարելու դեպքում։ Մեր կոդը կկատարվի այնքան ժամանակ մինչև eax-ում եղած արժեքը լինի փոքր կամ հավասար 10-ի։ 10-ից մեծ լինելու դեպքում այն կավարտվի և jle ինստրուկցիան չի կատարվի։

Դիտարկենք նաև զանգվածի հայտարարումը և դրանցում էլեմենտների արժեքների տեղադրումը։ Ինչպես գիտենք, զանգվածը տվյալների տեսակ է, որը կարող է պահել տարբեր նույնատիպ արժեքներ։ Օրինակ՝ զանգվածը կարող է պահել 60 հատ int տիպի արժեք, որոնք ներկայացնում են 5 տարվա խաղերի վաճառքից ստացված եկամուտը, կամ` 12 short տիպի արժեքներ, որոնք ներկայացնում են ամսվա մեջ օրերի քանակը։ Յուրաքանչյուր արժեք պահվում է զանգվածի առանձին էլեմենտի մեջ ու համակարգիչը հաջորդաբար պահում է բոլոր էլեմենտները հիշողության մեջ։ Զանգված հայտարարելու համար անհրաժեշտ է նախ և առաջ նշել, թե ինչ տիպի զանգված ենք ցանկանում հայտարարել, ապա նշել զանգվածի անվանումը և էլեմենտների քանակը։ Մեր օրինակում կպատկերենք int տիպի զանգվածի հայտարարումը։ C++ լեզվում զանգվածը կհայտարարենք հետևյալ կերպ`

Ինչպես տեսնում եք, զանգվածի էլեմենտներին արդեն իսկ վերագրել ենք արժեքներ։ Դա նշանակում է, որ arr[0] = 1, arr[1] = 2, arr[2] = 3, arr[3] = [4]:

Ասեմբլերական լեզվում զանգվածի հայտարարումը և արժեքների վերագրումը կարելի է պատկերել հետևյալ կերպ․

Կոդի այս կտորում կարելի է տեսնել երկու նոր ինստրուկցիաներ` push-ը և pop-ը։ Նախևառաջ հասկանանք, թե ի՞նչ է նշանակում push rbp-ն։ Այստեղ rpb-ն ռեգիստրի անուն է: Այն համարվում է պահպանված ռեգիստր, դրա համար պետք է պահել դրա արժեքը` մինչ այն օգտագործելը։ Հնարավոր է, որ mainrbp-ում շատ կարևոր ինֆորմացիա է պահում և կբողոքի, եթե այն փոխենք, բայց եթե մենք rbp-ի արժեքը դնենք իր տեղում մինչև այն վերադարձնելը, ապա main-ի համար rbp-ի օգտագործումը կլինի նորմալ։ Ինչպես կարելի է ենթադրել մեր կոդից՝ pop ինստրուկցիայի միջոցով էլ rbp-ի արժեքը դնում ենք իր տեղում։ Առանց push և pop հրամանների main-ը այսպես ասած՝ կզայրանա, որ իր իրերը խառնաշփոթի ենք վերածել, իսկ իսկական ծրագրում դա բավականին բարդ խնդիր կառաջացնի։

Կոդի 2-րդ տողում տեսնում եք mov rbp,rsp հրամանը։ rsp ռեգիստրում պահվում է այսպես կոչված՝ stack pointer-ը, որը ցույց է տալիս վերջին օգտագործված բայթը, ինչը նշանակում է, որ այս տողը կատարելիս rbp-ի մեջ է պահվում վերջին օգտագործված բայթը։

DWORD PTR-ն օգտագործում ենք ցույց տալու համար, որ մեկ ամբողջական օպերանդի չափը 32 bit է, իսկ [rbp-16]-ը մեր առաջին էլեմենտի հասցեն է։ Հասցեները ցույց տալու համար օգտագործում ենք քառակուսի փակագծեր։ Առաջին էլեմենտի հասցեի մեջ տեղադրում ենք արժեքը այնպես, ինչպես տեսնում եք կոդում։ Կարելի է նաև նկատել, որ հասցեները նվազում են 4-ով, ինչը նշանակում է, որ 1 էլեմենտը զբաղեցնում է 4 բայթ տարածք։

Տեղադրելով բոլոր արժեքները համապատասխան հասցեներում, հետ ենք տեղադրում rbp-ի արժեքը և ret ինստրուկցիայով ղեկավարումը փոխանցում ենք stack-ում գտնվող վերադարձվող հասցեին։

Նմանատիպ և այլ օրինակներ կարելի է բերել անվերջ։ Ամեն անգամ կոդին ավելացնելով մեկ նոր տող, մեկ նոր ֆունկցիա, նոր տիպ կամ փոփոխական՝ կարող ենք ստանալ մի որևէ նոր բան և սկսել ուսումնասիրել դրա աշխատանքը։ C++-ը և ասմբելերը լեզուներ են, որոնք արժանի են նման ուսումնասիրությունների, քանի որ դրանց միջոցով կարող ենք բացահայտել մեզ դեռևս անծանոթ գեղեցիկ և բարդ աշխարհներ։

Code Republic-ը ծրագրավորման գիտահետազոտական կենտրոն է, որն ունի նաև ուսումնական բաժին։ Ուսումնական բաժնում խմբավորում ենք խորացված ծրագրավորումը մաթեմատիկայի, ֆիզիկայի և ինժեներության հետ։

Մենք ջանք ու ժամանակ չենք խնայում և ստեղծում ենք այնպիսի որակյալ նյութեր, որոնք ցույց են տալիս ծրագրավորման իրական կողմը` արվեստը: Առայժմ դա ստացվում է, իսկ պատճառը պարզ է.

մենք սիրում ենք այն, ինչ անում ենք։

Ձգտում ենք ունենալ ծրագրավորման, մաթեմատիկայի, ֆիզիկայի և ինժեներության խորացված լավագույն դասընթացները և վարձավճարը սահմանել ամսական հնարավոր նվազագույնը` 42 000 դրամ։ Խոստանում ենք երբեք չթանկացնել, իսկ շատ ու շատ անվճար դասընթացներ էլ տեղադրել YouTube-յան մեր ալիքում, այստեղ՝

Բոլոր ցանկացողները կարող են ստեղծել և տեղադրել նոր դասընթացներ, կամ, ինչու ոչ, գրել հայալեզու հոդվածներ Medium-ում։ Համագործակցության համար գրեք մեզ contact@coderepublic.am հասցեով։ Եվ, իհարկե, հետևեք մեզ այլ սոց. ցանցերում. Facebook, Instagram, Telegram, և որ ավելի կարևոր է՝ LinkedIn, տեղադրում ենք միայն օգտակար նյութեր։

Ջանք ու եռանդ չենք խնայում լուծելու երկրում գլխավոր խնդիրներից մեկը՝ որակյալ ծրագրավորող-ինժեներների կրթումը։ Ժամանակատար է, դժվար է, բայց կանգ չենք առնում։

Ընտրել ենք բա՛րդ ճանապարհը

--

--