Շատերի համար C++ լեզուն առաջին հերթին հետաքրքիր է նրանով, որ թույլ է տալիս անմիջականորեն գործ ունենալ հիշողության հետ ՝ օգտագործելով ցուցիչները (pointers)։ Բայց հիշողությանը ուղղակիորեն դիմելը նաև անկանխատեսելի հետևանքներ կարող է առաջացնել ;)։
Ցուցիչները առաջին հերթին հետաքրքիր են նրանով, որ թույլ են տալիս դիմել հիշողության որոշակի հասցեների և խմբագրել դրանք։ Եվ ահա այստեղ է, որ առաջանում են բազմաթիվ գլխացավանքներ, որոնք կարող են հանգեցնել անկանխատեսելի հետևանքների։ Այս հոդվածում կներկայացնենք մի իրավիճակ, երբ տվյալների պարզ տիպի ցուցիչի (օրինակ int*) միջոցով կարելի է ստանալ և փոխել դասի private անդամ փոփոխականի արժեքը, այդպիսով խախտելով դասի ինկապսուլացումը !: Դա անելու համար հարկավոր է իմանալ, թե ինչպես է դասի օբյեկտը (նմուշը) պահվում հիշողության մեջ:
Դիցուք ունենք հետևյալ դասը․
class foo { int a; char c; void bar(); };
Ինչպես ցույց է տրված նկարում, դասի օբյեկտը հիշողության մեջ զբաղեցնում է անընդհատ տիրույթ և այդ տիրույթը զբաղեցնում են միայն դասի անդամ փոփոխականները, տվյալ դեպքում օբյեկտի չափը կլինի 5 բայթ ( int a 4 բայթ + char c 1 բայթ )՝ չհաշված հավասարեցումը ! : Եթե անգամ դասը չի պարունակում ոչ մի անդամ փոփոխական, դրա օբյեկտի չափը տարբեր է զրոյից, իսկ թե որքան է դրա չափը, կախված է կոնկրետ մեքենայից և կոմպիլյատորից։
Դասի անդամ ֆունկցիաները որևէ կերպ չեն անդրադառնում հիշողությամ մեջ օբյեկտի կառուցվածքի վրա։ Պարզ ասած, օբյեկտի զբաղեցրած չափը հիշողության մեջ կախված է միայն դրա անդամ փոփոխականների գումարային չափից, իսկ եթե դասը նաև պարունակում է վիրտուալ ֆունկցիաներ և/կամ գտնում է ժառանգման շղթայի մեջ այնպիսի դասերից, որոնք պարունակում են վիրտուալ ֆունկցիաներ, ապա օբյեկտի չափի մեջ ներառվում է նաև վիրտուլ ֆունկցիաների աղյուսակի ցուցիչի (vptr) չափը (եթե դասը գտնվում է ժառանգման շղթայի մեջ այնպիսի դասերից, որոնք պարունակում են վիրտուալ ֆունկցիաներ, ապա տվյալ դասի օբյեկտը պարունակում է նաև բազային դասերի վիրտուալ ֆունկցիաների աղյուսակների ցուցիչները)։ Հիմա կասեք, իսկ ինչպե՞ս է այդ դեպքում կանչվում է bar() ֆունկցիան, եթե այն չի մտնում օբյեկտի հիշողության մեջ։ Ամեն ինչ շատ պարզ է 🙂 ։ Իրականում low level տեսակետից դասի անդամ ֆունկցիան քիչ է տարբերվում գլոբալ ֆունկցիայից։ Տարբերությունը կայանում է նրանում, որ դասի անդամ ֆունկցիայի կանչի դեպքում կոմպիլյատորը գիտի այն օբյեկտի հասցեն, որի միջոցով կանչվում է տվյալ ֆունկցիան և որպես թաքնված պարամետր տվյալ անդամ ֆունկցիան կոմպիլյատորի կողմից ավտոմատ ստանում է this ցուցիչը (տվյալ օբյեկտի հասցեն)։ Այսինքն, եթե ունենք․
foo f; f.bar();
պսևդոկոդով f.bar()-ը կարելի է գրել հետևյալ կերպ․
foo::bar(&f);
Լավ թողնենք ֆունկցիաների վերաբերյալ փոքրիկ լիրիկական զեղումը և անցնենք private -ին։ Ինչպես գիտենք, եթե դասի փոփոխականները հայտարարված են private, ապա դրանք հասանելի են միայն տվյալ դասի անդամ ֆունկցիաների և friend -ների համար։ Բայց, եթե կարողանանք ստանալ դասի օբյեկտի հասցեն և հաշվի առնելով այն, որ օբյեկտը հիշողության մեջ զբաղեցնում է հաջորդական անընդհատ տիրույթ, ապա կկարողանանք նաև դիմել դրա անդամներին !!! Ասվածը գրված է․
#include <iostream> class foo { private: int a; public: int get_a(){return a;} }; int main() { foo obj; // 1 int* p = (int*)&obj; // 2 *p = 27; // 3 std::cout << "Data member value " << obj.get_a() << std::endl; // 4 return 0; }
Բերված կոդի 1 տողում սովորականի պես ստեղծում ենք foo դասի obj օբյեկտը, իսկ ամենահետաքրքիրը կատարվում է 2-րդ տողում․ 2 – րդ տողում (int*)&obj գրառման միջոցով ստանում ենք obj օբյեկտի հասցեն, կամ որ նույնն է, օբյեկտի հասցեական շեղումը հիշողության մեջ։ Օբյեկտի հասցեն հիշողության մեջ ցույց է տալիս այն կետը, որտեղից սկսում է օբյեկտի պարունակութունը, հետևաբար այն միաժամանակ հանդիսանում է նաև օբյեկտի առաջին փոփոխականի հասցեն (մեր մոտ int a անդամ փոփոխականի), այսինքն դիցուք obj օբյեկտի հասցեն է 0x40000000, դա նշանակում է, որ a փոփոխականի հասցեն նույնպես կլինի 0x40000000 կամ որ նույնն է &obj, իսկ c փոփոխականի հասցեն կլինի 0x40000004 (char – տվյալների տիպը հիշողության մեջ զբաղեցնում է 1 բայթ)։ 2 -րդ տողում մենք int* տիպի p ցուցիչին վերագրում ենք մեր օբյեկտի հասցեն, դրանից հետո p ցուցիչը կհղվի մեր obj օբյեկտի շեղման վրա (հիշողության մեջ օբյեկտի սկզբի վրա), իսկ դա նշանակում է, որ 3-րդ տողում *p = 27 գրառումով մենք փոխում ենք p հասցեով 4 բայթ տարածք հիշողության մեջ, դե կռահեք խնդրեմ, թե դա որ փոփոխականի տարածքն է հիշողության մեջ ;): Եվ վերջապես 4-րդ տողում տպելով մեր a private անդամ փոփոխականի արժեքը իրոք համոզվում ենք որ այն հավասար է 27 -ի 😀 😉
Comments: no replies