PDA

نسخه کامل مشاهده نسخه کامل : RefrenceType And ValueType



Msba
28-03-2013, 01:11
امروز به صورت اتفاقی متوجه شدم که برنامه نویسان دو مطلب را فراموش می کنند و این فراموشی سبب می شود که خطاهای جبران ناپذیری ایجاد کنند. در واقع یک حفره امنیتی در نرم افزار هاشون ایجاد کنند. این اشتباه ممکن است باعث نفوذ هکر ها در سیستم استفاده کنندگان شود، خصوصا نرم افزار هایی که در محیط های شبکه ای اعم از اینترنت و LAN استفاده می شوند.
البته این فقط یک احتمال و یک هشدار است و ممکن است هیچگاه اتفاق نیوفتد.
توضیحات کمی پایه ای تر خواهد بود تا سطح های مختلف بتوانند استفاده کنند.

اصل مطلب:
دو نوع از منابع حافظه در .NET وجود دارد. منابعی که به مانند مقادیر بررسی شده و منابعی که عددی بررسی نخواهند شد.
منابعی که به صورت عددی بررسی خواهند شد شامل :
متغیر های عددی :

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
به همراه unsigned آن ها نظیر uint .
متغیر های Enum .
و مهم تر از همه struct ها.
هر struct ی که توسط برنامه نویس تعریف شده باشد و یا اینکه در .Net وجود داشته باشد به عنوان یک عنصر مقداری شناخته می شود.
به این دسته همان طور که مشخص است ValueType گفته می شود.
بار ها از این دسته استفاده شده است و عموما در این دسته برای برنامه نویسان مشکلی وجود نخواهد داشت.
-----------------------
دسته ی دوم شامل:
کلاس ها، چه برنامه نویس ایجاد کند چه از .Net باشد.
delegate ها
interface ها
عناصر object
string ها
dynamic ها
تمامی آرایه ها حتی اگر نوع آن ها int باشد.(چرا؟ چون که تمامی آرایه ها از System.Array ارث بر هستند و این کلاس است! در ضمن خود System.Array از object ارث برده است که آن هم refrenceType هست.)
-----------------------
یک توضیح کوتاه لازم است که بیان شود چرا این عناصر با هم متفاوت هستند:
نگاهی به حافظه می اندازیم، سه بخش کلیدی وجود دارد:
HEAP
Stack
Reserved
بخش سوم عموما مربوط به سیستم های عامل بوده و توسط آن محافظت می شود و قابل دسترسی نیست اما در دو نوع دیگر برنامه نویس تصمیم گیر است.
متغیر های محلی، آرگومان های متد ها؛ و در دید نوع گرایی عموما ValueType ها در stack قرار می گیرند.( کدام stack ؟ هر Thread برای خود یک stack از بخش stack پروسس اصلی بر می دارد. منظور این استک می باشد.)
تمام content های refrence-Type ها در Heap قرار می گیرد.( توضیح اضافه: دو نوع heap وجود دارد: managed heap و unmanaged heap، اینجا منظور heap است که manage است و توسط garbage collector قابل دسترس هست.)
پس با این تعاریف یک کلاس می تواند هم در استک فضا گرفته باشد و هم در هیپ.

حال بحث مربوط می شود به دو اصل: class و struct
شما زمانی که از struct استفاده می کنید یک فضا از stack میگیرید اما زمانی که از class استفاده می کنید Heap نقش خود را بازی می کند. ( در هر صورت حافظه manage شده است که در C# و یا VB.net کسی به این موارد نیاندیشد و از معایب این عمل سست شدن برنامه ی برنامه نویس خواهد بود.)
مهم ترین بحث در این دو دسته که باعث شد این مطالب گفته شود by ref و by value است.
اگر یک value-Type به یک متد ارسال شود در حالت عادی مقدار آن منتقل می گردد. و هر تغییری که در آن انجام شود هیچ تغییری در اصل متغیر نخواهد داد. این مطلب را بار ها دیده ایم.
اگر بخواهیم که متد یا تابع ما مقدار را تغییر دهد یک ref مشکل را حل می کند، اما در نوع دوم یعنی RefrenceType ها اینگونه نیست.
یعنی اگر یک کلاس به یک متد ارسال شود و content های آن اعم از Field، Property، Method و... تغییر کند خود شی تغییر کرده است و در بازگشت از متد این تغییرات قابل استفاده هستند. این رفتار بار ها باعث خطا برای برنامه نویس ها شده است.
پس by ref یا همان ref در این نوع چه استفاده ای دارد؟
Ref کردن یک Refrence-Type به معنای اجازه دادن تعریف مجدد آن توسط متد است. یعنی یک instance جدید.یک null و.... (مثال کد در ادامه)
فرض خانه ی 100 رم توسط شی من اشغال شده باشد آنگاه:
بدون ref : شی منتقل شده و تنها اجزای آن تغییر می کند. یک instance جدید هیچ تغییری در شی ایجاد نمی کند و یا اینکه شی را نمی شود null کرد.
با ref : با یک instance جدید، نه تنها مقادیر را تغییر می دهم بلکه هم اکنون شی من در خانه ی (مثال) 150 قرار دارد.

حال مشکل امنیت مشخص گردید. اگر شما یک DLL را نوشته اید و فراخوانی می کنید که به آن یک شی RefrenceType می فرستید اگر لازم است که شی شما مجدد تعریف گردد که مشکلی وجود ندارد اما اگر فقط مقادیر می خواهند تغییر کنند پس by ref بی معنی است. چرا چون یک هکر dll خود را جایگزین dll شما می کند و شی شما را کامل در دست می گیرد و با تعییر شی شما درنقطه مورد نظر و الی آخر....

پس می بایست در ارجاع با مقدار یا منبع (آدرس در unmanaged) دقت شود.
به این کد دقت کنید:

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
اول مثال را امتحان کنید.
در یک بخش از مثال تغییر string یک struct مد نظر است.
*تعاریف خط بعد از میکروسافت است*
همان طور که مشخص است string یک RefrenceType هست و Struct یک value . پس رشته انتقالی چه می شود؟ رشته زمانی که به صورت رشته منتقل شود تغییرات را باید بپذیرد؛
اما زمانی که در قالب یک Struct منتقل می شود تحت رفتار آن خواهد بود.(مبحث مربوط به تئوری های شی گرایی) و در نتیجه تغییرات آن منتقل نمی گردند.
تناقض در خط آخر:
حال تناقض اینجا خواهد بود:

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
و این اجراگر:

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
همان طور که پس از اجرا می بینید رشته تغییر نکرد. پس رشته می شود استثنایی برای refrence-Type ها .
یعنی چه در struct باشد و چه به صورت تک نیازمند ref برای تغیرات هست. اما در یک کلاس نیازمند ref نیست.
نکته:
اگر ارجاع کلاس باشد ref برای String ها نیاز نیست. اگر یک string را از یک کلاس مستقیم ارجاع کنیم نیازمند ref خواهد بود.

موفق باشید.

Msba
28-03-2013, 01:17
برای مطالعات بیشتر می توانید از منابع زیر نیز استفاده کنید:

[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]

_H2_
29-03-2013, 11:27
سلام
ممنون. توضیحات بسیار خوبی بود.
در مورد تفاوت رفتار حافظه پشته و هیپ تاپیک مفیدی نداشتیم.
صرفاً جهت تکمیل بحث و آنکه تاپیک مرجع کاملی داشته باشیم با اجازه تان چند نکته را اضافه کنم ...

مطمئن شوید که قبل از خواندن این مطالب دو پست قبلی را خوانده و متوجه شده اید ...

==============

اول آنکه در عمل interface ها برابر ساختار یا کلاسی که در آن تعریف شده اند رفتار میکنند.
اگر توضیحات دوستمان جناب Msba را خوانده باشید کد زیر و نتیجه ان واضح تر از توضیح خط بالا خواهد بود:

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
ولی این درست است که ما اغلب interface ها را در کلاس ها استفاده میکنیم و...

==============

مطلب دوم هم آنکه string خیلی هم استثنا نیستند.
ساختارهایی داریم که در دات نت استثنا هستند و به ذات مدیریتی CLR برمیگردند ولی بدون شک String یکی از انها نیست.

یک کلاس کاملاً عادی با رفتار کاملاً منطبق بر اصول حافظه هیپ و نقل و انتقال اشیای این حوزه است.
دلیل اصلی که رشته های متنی (String) و آرایه ها (Array) کلاس هستند و نه ساختار آن است که این متغییر ها میتوانند حجم های بالایی از اطلاعات داشته باشند و اگر قرار باشد در تمام نقل و انتقالات مدام نسخه جدیدی از انها کپی برداری شود (مانند struct ها) بازدهی برنامه های دات نت بشدت کاهش می یافت.

در نتیجه String ها و Array ها class هستند و ساختار حافظه هیپ دارند تا مدام کپی نشوند.
در واقع با پاس دادم هر دو اینها به یک تابع فقط در حد 4 یا 8 بایت اطلاعات اشاره گر حافظه به تابع مذکور انتقال پیدا میکند.


حالا تنها تردیدی که وجود دارد ان است که پس چرا نمیتوانیم string را در تابع گیرنده تغییر دهیم و همه جا تغییر کند؟
اولاً میتوانیم تغییر دهیم ، بگذارید در اخر به این بپردازیم ...

دوماً نکته انجاست که کلاس string هیچ خصیصه set یا تابع تغییر دهنده ای ندارد.
یعنی به نوعی یک کلاسی است که بعد از نمونه سازی و new شدن دیگر هیچ خصیصه یا متدی ندارد که ان را تغییر دهید، کاملاً readonly است.
یک ساختمانی که خروجی دارد ولی هیچ وردی ندارد!
وقتی هم شما عملیاتی روی ان انجام میدهید در واقع یک نمونه جدید برای شما new میشود ... !
با دقت بخوانید مطمئناً متوجه میشود، واقعاً درکش خیلی زیبا و لذت بخش است.

مثلاً کلاس زیر را هم اگر استفاده کنید دقیقاً مشابه کلاس String رفتار میکند...

برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید

در کد فوق هم Class1 هیچ متد و خصیصه و رفتار و... و... ندارد که محتوای داخلی اش را عوض کند.
اگر هم تابع و اپراتور و... داشته باشد که به ظاهر تغییری در آن بدهد (مانند اپراتور جمع در کلاس فوق)، خروجی آن نمونه جدیدی از Class1 است و ذات هر Class1 تولید شده ای پس از new دیگر تغییر نمیکند.
این دقیقاً همان کاری است که مایکروسافت در کلاس String انجام داده و شاید همانطور که دوستمان اشاره کردند در ظاهر تفاوتی با سایر کلاس ها داشته باشد ولی در واقع هیچ تفاوتی ندارد و یک کلاس کاملاً عادی است.

ادامه مطلب اول!!!
برای اطمینان بیشتر انکه String یک کلاس عادی است و با اشاره گر هیپ اش جابجا میشود در اخر هم میتوانید کد ساده و زیبای زیر را را در مد unsafe آزمایش کنید:


برای مشاهده محتوا ، لطفا وارد شوید یا ثبت نام کنید
(در عمل هیچگاه انجام ندهید، پیامدهای بسیار نامناسب و وسیعی میتواند در برنامه تان داشته باشد، بسیار بدتر از goto !)

موفق باشید.