Գրադարանների հրաշքների աշխարհում

Որպես աշխատանքային լեզու կդիտարկենք C++ լեզուն, իսկ որպես կոմպիլյատոր՝ GCC փաթեթի մաս կազմող g++ կոմպիլյատորը։ Նյութում կարող են լինել անճշտություններ, քանի որ ես ո՛չ այդ լեզվի, ո՛չ էլ նշված կոմպիյլատորի հեղինակը չեմ հանդիսանում։

Ամեն ինչ սկսվում է մեկ ֆայլից բաղկացած պարզագույն “Hello, World!” ծրագրից՝

#include <iostream>

int main()

{

std::cout << “Hello, World!\n”;

return 0;

}

Կոմպիլյատորն ակտիվացնելու պարագայում առաջին հերթին կոդն սկսվում է մշակվել նախապրոցեսորի (pre-processor) կողմից։ Այն մի ծրագրային համակարգ է, որի խնդիրն է հանդիսանում ծրագրի կոդում առկա հրամանագրերի կատարումը։ Հրամանագրերը սկսվում են վանդականիշով և պետք է լինեն նախապրոցեսորին հայտնի բառերի բազմությունից։ Մասնավորապես՝ բերված օրինակում առկա է #include հրամանագիրը, որի կատարման ընթացքում նախապրոցեսորը գտնում, բացում է հրամանագրի պարամետրի միջոցով տրված ֆայլը, այն ռեկուրսիվ կերպով ենթարկում է նախամշակման և արդյունքում ստացվածը ֆիզիկապես տեղադրում է մեր կոդի մեջ՝ առաջին տողի փոխարեն։

Արդյունքում ստացվում է ահռելի չափերի հասնող մի ծրագիր, բայց դա վախենալու չէ, քանի որ այդ կոդի մեծ մասը կոմպիլյացիայի հետագա փուլերում անտեսվելու է։

Հաջորդիվ ակտիվանում է թարգմանիչը, որը C++ լեզվից կոդը թարգմանում է համապատասխան պրոցեսորի ասեմբլեր լեզվին, օրինակ՝ X86 assembly լեզվին։ Այդ ընտրությունը կատարում է ծրագրավորողը՝ ելնելով իր ծրագրի թիրախային համակարգի ճարտարապետությունից։

Հաջորդ քայլով ասեմբլեր կոչվող կոմպոնենտը մշակում է ստացված ֆայլը՝ նրանից ստանալով օբյեկտային ֆայլ։ Այն արդեն չի պարունակում տեքստային կոդ։ Ծրագրի կոդը թարգմանվում է մեքենայական լեզվի։ Սակայն, բացի կոդից, օբյեկտային ֆայլում զետեղվում է նաև մի աղյուսակ, որի մեջ նշված է լինում նախնական կոդում առկա ֆունկցիաների և փոփոխականների մասին տեղեկատվություն։ Մասնավորապես՝ աղյուսակից կարելի է տեսնել, թե կոնկրետ ինչ ֆունկցիաներ և փոփոխականներ կան սահմանված այդ ֆայլում, ինչ հասցեներ ունեն դրանք։ Ինչպես նաև, կարելի է տեսնել, թե ինչեր օգտագործված՝ չլինելով հայտարարված այդ կոդում։ Այդ աղյուսակին անվանենք սիմվոլների աղյուսակ։

Մեր օրինակում սիմվոլների աղյուսակում կլինի ինֆորմացիա main() ֆունկցիայի և ostream կլասում առկա որոշ ֆունկցիաների մասին։ Առաջինը հենց հատյարարված է մեր կոդում, այսինքն աղյուսակում կունենա հստակ հասցե, իսկ մնացածների հաշվով հայտնի կլինի, որ նրանք օգտագործված են, բայց հայտարարված չեն մեր կողմից (տե՛ս պատկեր 1)։ Պատկերում բերված արդյունքը ստացվել է mc — Midnight Commander ծրագրի դիտորդ (viewer) կոմպոնենտի կիրառմամբ։

Պատկեր 1։ Օբյեկտային ֆայլի սիմվոլների աղյուսակը

Ինչպես տեսնում ենք՝ main() ֆունկցիան ունի զրոյական հասցե, իսկ, օրինակ, std::ios_base::Init::Init() կոնստրուկտորի հասցեի մասին տեղեկություն չունենք։ Այլ կերպ ասած՝ օբյեկտային ֆայլում կա ինֆորմացիայի պակաս։ Այդ պակասը լրացնելու համար էլ ստեղծված է կոմպիլյատորի հաջորդ փուլում ակտիվացվող կոմպոնենտը՝ linker-ը։ Այն գործում է ծրագրի բոլոր ֆայլերից ստացված օբյեկտային ֆայլերի վրա միանգամից և որպես արդյունք տալիս է վերջնական կատարվող ֆայլ (executable file)։

Վերջնական ֆայլում այլևս չկա պակասող ինֆորմացիա։ Լինքերը հայտնաբերում է պակասող ֆունկցիաների մասին անհրաժեշտ ինֆորմացիան և դրանով լրացնում է օբյեկտային ֆայլը։ Այդ լրացուցիչ ինֆորմացիայի համար լինքերն օգտվում է ծրագրային գրադարաններից։ Այս դեպքում լինքերն օգտվում է C++ լեզվի ստանդարտ գրադարանից։

Պատկեր 2։ Կատարվող ֆայլի սիմվոլների աղյուսակը։

Ինչպես տեսնում ենք պատկեր 2-ում՝ վերջնական, կատարվող ֆայլում կա ինֆորմացիայի լրացում։ main() ֆունկցիան տեղաշարժվել է, ստացել է նոր հասցե, իսկ վերը նշված կոնստրուկտորի մասին ավելացել է տեղեկություն առ այն, որ դա գտնվում է GLIBCXX_3.4 գրադարանում։

Ծրագրային գրադարանները պարունակում են օգտակար կոդ, առանց որոնց անհնար կլիներ մշակել մեծ ծրագրային համակարգեր։ Գրադարաններն օգնում են ստանալ առավել հարմար և արդյունավետ ինտերֆեյս դեպի օպերացիոն համակարգերի տրամադրած ռեսուրսներն ու ինտերֆեյսերը։

Գոյություն ունի գրադարանների կիրառման երկու մեթոդ՝ ստատիկ և դինամիկ (statically linked library և dynamically linked library)։ Սրանցից յուրաքանչյուրն ունի իր առավելություններն ու թերությունները։ Մանրամասն ուսումնասիրենք դրանք։

Ստատիկ կցվող գրադարաններն իրենցից ներկայացնում են օբյեկտային ֆայլեր պարունակող արխիվներ։ Դրանք ստեղծվում են ar ծրագրի միջոցով և պարունակում են մի շարք օբյեկտային ֆայլեր։ Այլ կերպ ասած՝ ստատիկ գրադարանը պատրաստ չէ կատարման համար, մինչև չանցնի վերջնական լինքինգի փուլ։

Պատկեր 3։ Ստատիկ կցվող արխիվի պարունակությունը։

Լինքերը, ստատիկ գրադարանի հետ գործ ունենալիս, այն դիտարկում է որպես պակասող ինֆորմացիայի աղբյուր՝ այնտեղից բերելով պակասող ֆունկցիաների իրականացումը և կցելով մեր ծրագրին։ Ավելի կոնկրետ՝ լինքերը բացում է արխիվը և նրանում գտնվող օբյեկտային ֆայլերից յուրաքանչյուրում փնտրում է պակասող ֆունկցիաները։ Գտնելու դեպքում համապատասխան օբյեկտային ֆայլը ֆիզիկապես կցում է մեր ծրագրին, ինչի արդյունքում լրացվում է ֆունկցիայի հասցեի մասին ինֆորմացիան։

Հաջորդ տարբերակը դինամիկ կցվող գրադարանն է։ Այն իրենից ներկայացնում է կատարվող ֆայլի պես ֆայլ, որն ունի երկու հիմնական տարբերություն՝ չունի մուտքի կետ և միջի կոդը հանդիսանում է դիրքից անկախ հասցեավորվող կոդ՝ position independent code. Այսպիսի գրադարանը կարելի է բեռնել կատարվող ծրագրի հիշողության տիրույթ՝ ստանալով պակասող ֆունկցիաների մասին ինֆորմացիան արդեն կատարման ընթացքում։

Պատկեր 4։ Դինամիկ կցվող գրադարանի սիմվոլների աղյուսակը

Դինամիկ կցվող գրադարանի դեպքում լինքերը պակասող ինֆորմացիան լրացնում է մասնակիորեն՝ նշելով, թե ո՛ր գրադարանում է գտել անհրաժեշտ ֆունկցիան։ Մնացած գործն անում է բեռնիչը (loader)։ Այն հանդիսանում է օպերացիոն համակարգի կոմպոնենտ և իր հիմնական ֆունկցիան ծրագիրը և նրա աշխատանքի համար անհրաժեշտ գրադարանների բեռնումն է։ Արդեն բեռնված գրադարանում բեռնիչը գտնում է անհրաժեշտ ֆունկցիան և նրա հասցեի մասին ինֆորմացիան ավելացնում է կատարվող ֆայլի մեջ՝ մասնավորապես փոփոխելով ֆունկցիաների կանչերում առկա հասցեները։

Կարևոր է հասկանալ, որ անկախ այն բանից, թե գրադարանի կցման ո՛ր տարբերակն է կիրառված, արդյունքում, ծրագրի կատարման ընթացքում, ունենում ենք նույն պատկերը՝ ողջ անհրաժեշտ կոդը բեռնված է հիշողություն և ուղղված են բոլոր ֆունկցիաների կանչերի հրամանները։

Անդրադառնալով այս երկու մոտեցումների առավելություններին և թերություններին արժե նշել, որ ստատիկ կցվող գրադարանները չունեն արտաքին կախվածություններ, որոնցով օժտված են դինամիկ գրադարանները Մյուս կողմից՝ ստատիկ գրադարաններում առկա կոդը չի կարող թարմացվել գրադարանի արտադրողի կողմից, մինչդեռ դինամիկ գրադարանը կարող է թարմացվել։

Իհարկե այս հոդվածը չի ծածկում գրադարաններին առնչվող բոլոր հարցերը, բայց սա լավ սկիզբ կարող է լինել հետաքրքրված ընթերցողների համար։

Հեղինակ՝ Վահրամ Մարտիրոսյան