PDA

نسخه کامل مشاهده نسخه کامل : وضعیت حافظه در استفاده از کلاس



Msba
16-04-2015, 11:23
شاید برای اینکه بتوانم سوالم را خوب مطرح کنم باید یکمی توضیحات بدهم.

ما دو بخش حافظه اصلی داریم که بعد از کامپایل اطلاعات اطلاعات در آن قرار می گیرد:
- حافظه ی Code
- حافظه ی Data
حافظه ی Code به حافظه ی RO و Text. نیز معروف است.
حافظه ی Data که به RW ، ZI و BSS. نیز معروف است.

- مثلا موقعی که یک struct تعریف می کنیم و آن را در یک تابع تعریف می کنیم. مقادیر این struct در stack تعریف می شود که بخشی از حافظه ی Data است.
- اگر یک متغیر local تعریف کنیم آن در stack تعریف می شود.
- اگر یک struct رو global تعریف کنیم، مقادیر این stack در بخش اصلی Data قرار می گیرد. متغیر هم همین طور.
- اگر یک آرایه را malloc کنیم اطلاعات در heap که باز هم بخشی از Data است قرار می گیرد.
- اگر یک ساختمان را new یا malloc کنیم نیز باز Heap است و در Data قرار می گیرد.
- یک متغیر const در حافظه ی Code تعریف می شود.
-define# ها هم بسته به casting در Code و یا Data تعریف می شوند.

در سیستم های EABI که نرم افزار نوشته شده باید تحت یک OS استفاده شود. حافظه ی Data که درون رم است.
سوال اول) در مورد اینکه حافظه ی Code در این برنامه ها کجاست اطلاعاتی ندارم، نمی دانم که به رم بارگذاری می شود و یا از روی هارد اجرا می شود. کدام؟

در سیستم های none-eabi که به Bare Metal نیز معروف هستند (نظیر میکروکنترلرها) حافظه ی code همان Flash داخلی و یا خارجی آنهاست و حافظه ی Data رم آنهاست.

حال می ماند بحث کلاس ها. تا آنجا که من می دانم زمانی که کلاسی به صورت محلی new شود خود متغیرش در stack و مقادیرش در heap خواهد بود. تا اینجا مانند struct هاست.
سوال دوم) کلاس ها دارای توابع نیز هستند. وضعیت متغیرهای آن ها و عناصر static که مشخص است. این توابع درونی کلاس ها در هنگام new کردن چه می شوند؟ به رم بارگذاری می شوند؟ یا یک اشاره گر به حافظه ی Code خواهند داشت؟ پاسخ این سوال می تواند به مدیریت Heap کمک کند.

ممنون.

god of war 2
16-04-2015, 19:43
سوال اول) در مورد اینکه حافظه ی Code در این برنامه ها کجاست اطلاعاتی ندارم، نمی دانم که به رم بارگذاری می شود و یا از روی هارد اجرا می شود. کدام؟
ببینید تقریبا مدیریت حافطه در ساختار ABI و EABI(برای سیستم های embeded) مشابه هست و از یک الگوی استاندارد پیروی میکنند که در پست خودتون توضیح دادید. اصلا ساختار ABI برای اهداف دیگری مثل نحوه فراخانی توابع, نحوه پاس دادن ارگومان ها, نحوه استفاده از رجیسترها و ... برای تولید یک کد اسمبلی سازگار و مشترک بین سیستم های مختلف ایجاد شده و این ساختار در لایه مدیریت حافظه تفاوتی در استفاده از سخت افزار ندارن یعنی مواردی که در حالت استاندارد در RAM قرار میگیرند در این ساختار هم در RAM قرار میگیرند فقط تفاوت در نحوه پیکر بندی هست که مثلا در ساختار EABI ب دلیل کمبود مقدار فیزیکی رم ممکنه اندازه frame ها تغییر کنه و مواردی از این دست اعمال بشه.



سوال دوم) کلاس ها دارای توابع نیز هستند. وضعیت متغیرهای آن ها و عناصر static که مشخص است. این توابع درونی کلاس ها در هنگام new کردن چه می شوند؟ به رم بارگذاری می شوند؟ یا یک اشاره گر به حافظه ی Code خواهند داشت؟
کلاس ها و struc ها از هر لحاظ مشابه هم هستند و تنها فرق بین انها اینه که در حالت پیشفرض اعضای struct بصورت public در نظر گرفته میشن ولی برای کلاس بصورت private در نظر گرفته میشه. اما راجب نحوه ذخیره توابع, دستور العمل های موجود در یک تابع تماما در بخش Code ذخیره میشوند اما دو حالت کلی وجود دارد. حالت اول توابع غیر virtual که تنها یک نسخه از دستورالعمل ها در بخش code ذخیره میشود و هرگاه این تابع فراخانی شود توسط یک دستور jump به code segment اون تابع اجرا میشود و تعداد اشیاء ساخته شده در این حالت اصلا مهم نیست و هیچ فضای اضافه ایی برای اشیاء ساخته شده مصرف نمیشود یعنی اگز شما 1000 شی از کلاس ایجاد کرده باشید تنها یک نسخه از توابع در بخش code ذخیره میشود و برای تمام 1000 شی از همان یک نسخه استفاده میشود.
اما در حالت virtual شرایط متفاوت است. در این حالت برای هر تابع یک جدول (vtable) ایجاد میشه که شامل تمام نسخه های اون تابع میشه مثلا اگر شما از یک کلاس پایه 5 کلاس مشتق ایجاد کرده باشید یک جدول شامل 5 ادرس تابع تشکیل میشه. و هر شی از کلاس یک اشاره گر به تابع مخصوص خودش در جدول رو با خودش حمل میکنه. فضایی اضافی که در این حالت استفاده میشه شامل یک vtable برای هر کلاس و یک اشاره گر برای هر شی هست.
ویرایش:
البته ناگفته نماند که روش بالا لزوما روش استاندارد نیست و کامپایلرها بسته ب نوع پیاده سازیشون میتونن در حالت های بهینه تر عمل کنند و روش های بهینه تری رو بکار ببرند.

_H2_
16-04-2015, 22:33
سلام
ضمن تایید صحبت های god of war 2 ، در ادامه توضیحی برای سوال دوم تان حاضر کرده بودم که چنین است:
توابع در ناحیه Code درون RAM یکبار بارگذاری میشوند و اشاره گر شی (Object) که تابع از آن اجرا میشود به عنوان یک آرگومان (توسط کامپایلر) به تابع پاس داده میشود که این آرگومان خاص با نام this میشناسیم و در تابع قابل استفاده است.
بدیهی است در این شرایط بارگذاری 100 نمونه شی از یک کلاس فقط معادل 100 برابر متغییر های همان کلاس + یک بار حافظه برای کدها خواهد بود.
شاید تنها تفاوت توابع static در این است که نیاز به این اشاره گر ندارند.
فراخوانی یک تابع از یک نمونه کلاس برابر با فراخوانی ساده یک متد با یک پارامتر ورودی برای نمونه آن کلاس است، نکته فقط وجود یک پارامتر مخفی برای همه توابع عضو است.

در زبان های آزاد (unsafe) مانند ++C هیچ محیط runtime ای وجود ندارد و هیچ کس هیچ کجا هیچ اطلاعاتی از نوع و مفهوم و تعداد متغییرها ندارند، درست مانند ندانستن طول آرایه، نوع متغییر و توابع و... هم کاملاً ناشناخته است.
چیزی که میخواهم بگویم آن است که با دانستن آدرس یک نقطه از RAM هیچ کس و نه خود سیستم نمیداند آن نقطه (*void) به یک عدد اشاره میکند یا به یک نمونه کلاس، یعنی تفاوت int با یک کلاس مشخص نیست و واقعاً هم فرقی ندارند! فقط اعدادی بی مفهوم در آن نقطه هستند و فقط و فقط کامپایلر در زمان کامپایل پروژه نوع (type) را میدانسته... اجازه دهید مثالی ببینیم:

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
خیلی جالب است دقت کنید، از دید شی گرایی Class1 و Class2 و int هیچ وجه اشتراک و قابلیت تبدیل و نگاشت و... ندارند ولی از نظر مدل و چیدمان عناصر حافظه ++C هیچ تفاوتی بین Class1 و Class2 و int وجود ندارد! یعنی هر سه به یک شکل و قالب از RAM استفاده میکنند، هیچ سربار داده های اضافه وجود ندارد و با یک بیدقتی کوچک راحت میتوان آنها را بجای یکدیگر استفاده کرد! (چه مثبت و چه منفی)
با داشتن اشاره گر خام نقطه RAM (همان *void) کامپایلر هم نوع را گم میکند و تنها کسی که نوع را میدانست هم کنار میرود، در نتیجه خیلی راحت میتوان RAM تخصیص داده شده برای یک class را به یک struct نگاشت کرد و یا بلعکس و هیچ اتفاقی هم رخ نمیدهد! حتی تابع sum هم به راحتی Class اش را میفروشد و امکاناتش را نثار یک RAM تخصیص داده شده برای int (متغییر x) میکند، یعنی کامپایلر یک نقطه RAM را به عنوان this به sum داده و sum هم بدون هیچ فهم و درکی ، آن عدد را شروع Class1 خودش فرض میکند و روی آن کار انجام میدهد.
در همچین زبانی، مهم فقط و فقط عدد شروع نقطه RAM است + فهم زمان کامپایل از نوع متغییر اشاره شده توسط این عدد(اشاره گر) ولاغیر.
برای پیاده سازی شی گرایی و override و... هم فقط جدولی وجود دارد که شامل آدرس توابع است و این جدول (اشاره گرتوابع) در مشتق ها بروز میشود.

در اینجا جالب است به واسط IUnknown و IDispatch هم اشاره کنم که در دنیای COM که کمی قانون مندی را اجباری میکنند، و مسئول تبدیل نام های توابع به آدرس توابع و بازگرداندن ساختار کلاس ها و شمارش تعداد ارجاع و... بوده اند.

در ادامه شاید بد نباشد به زبان تخصصی خودم هم اشاره ای کنم...
این وضعیت در زبان هایی امن (safe) مانند #C ([ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]) و Java کمی (خیلی کم) متفاوت است، این زبان ها بیشترین حد مطالب را نگه داری میکنند و در زمان اجرا هم در اختیار قرار میدهند که میتوان نمود این کاتالوگ های متادیتایی را در قابلیتی بسیار جالب مانند Reflection مشاهده کرد.
در اینجا با داشتن متغییر یک شی در runtime میتوان قطعی گفت آن چه شی از چه نوعی است و چه توابع و خصایص و رویداد و... و... دارد.
در #C ([ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]) قبل از دیتای اصلی کلاس یک اشاره گر شی نوع داده (System.Type) قرار دارد، یعنی مانند آن است که بگویم هر کلاسی در #C ([ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]) یک متغییر حتمی و مخفی از نوع System.Type دارد.

یعنی یکی شی اصلی و یک شی نمونه سازی شده System.Type به حالت singleton (تک نمونه در کل برنامه)، دقت کنید که شی دوم هربار new نمیشود و حافظه گیری آن برای تعریف یک نمونه کلاس و 100 نمونه یک کلاس فرقی ندارد این همان اشاره گری است که با کلمه کلیدی typeof و یا متد GetType قابل دریافت است.
یعنی اگر اشاره گر نهایی کلاس را به دست آوریم برای خواندن دیتای آن باید IntPtr.Size بایت به جلو پیش برویم.

درضمن اگر گمراه نشوید باید عرض کنم که اشاره گرهای دات نت همه دوگانه هستند (**void) که خود مزیت های زیادی به همراه دارد که بماند...

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
دقت کنید که بدلیل مکانیزم هایی مانند defragment هرلحظه امکان دارد مکان یک شی یا داده در حافظه CLR جابجا شود (به کمک **void) پس دستوراتی مانند آنچه در فوق دیدید با دقت فراوان باید استفاده شوند و موارد متعددی برای تضمین عملکرد آنها باید رعایت شود که گمانم در بحث تاپیک خارج است و بجز موارد بسیار بسیار بسیار خاص کاربردی در برنامه های عادی #C ([ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]) ندارد و باید از این کدنویسی پرهیز کرد.

موفق باشید.

Msba
17-04-2015, 14:41
احساس می کنم که کلید تشکر کافی نیست. لذا از شما دو بزرگوار مکتوب تشکر می کنم.