والا گوگل چیز خوبیه ........
اختصاص حافظه داینامیک در C++
Dynamic Memory Allocation in C++
یکی از مباحث مهم و پرکاربرد در C++ تخصیص حافظه داینامیک برای متغیر ها می باشد. در حالت معمولی وقتی متغیری داخل برنامه تعریف می شود، آن متغیر داخل حافظه Stack قرار میگیرد. اما با اختصاص حافظه به صورت داینامیک برای یک متغیر آن متغیر داخل حافظه Heap قرار میگیرد. اختصاص حافظه داینامیک در زبان C++ کاربرد های زیادی دارد که در ادامه با برخی از آنها آشنا خواهیم شد. برای شروع بهتر است نگاهی به مفهوم حافظه های Stack و Heap داشته باشیم.
حافظه Stack: حافظه در داخل برنامه های C++ به دو بخش تقسیم می شود: حافظه Stack و حافظه Heap .حافظه Stack برای نگهداری متغیر های معمولی و نگهداری اطلاعات توابع در هنگام فراخوانی استفاده می شود. به طور کلی می توان گفت مهمترین کاربرد حافظه Stack در فراخوانی توابع و کنترل آنها می باشد. برای این که با طرز کار حافظه Stakc بیشتر آشنا شوید، می توان این بخش از حافظه را به ستونی از بشقاب های رو هم قرار گرفته تشبیه کرد. آخرین بشقابی که وارد شده، اول از همه از ستون بشقاب ها خارج می شود. این حالت به LIFO-Last In First Out معروف است. این مسئله در مورد توابع نیز سازگار است. زمانی که تابعی فراخوانی می شود این تابع به همراه تمامی متغیرهای محلی خودش در داخل حافظه Stack قرار می گیرد. با فراخوانی یک تابع جدید این تابع بر روی تابع قبلی قرار میگیرد و کار به همین صورت ادامه پیدا می کند. در حقیقت می توان گفت که بالاترین تابع در حافظه Stack تابعی است که هم اکنون در حال اجرا می باشد. زمانی که کار فراخوانی یک تابع تمام شد آن تابع به همراه تمام متغیرهای مربوطه از داخل حافظه Stack خارج می شود. برای روشن تر شدن موضوع حالت زیر را در نظر بگیرید. کلیه برنامه ها C++ با فراخوانی تابع main شروع میشوند. پس اولین تابعی که داخل Stack قرار میگیرد تابع main است. حال فرض کنید که بعد از تابع main تابع func1 و از داخل تابع func1 تابع func2 فراخوانی شود. در این حالت ابتدا تابع func1 در حافظه Stack روی تابع main و سپس تابع func2 بر روی تابع func1 میگیرد که حالت حافظه Stack به صورت زیر در می آید:
Stack Memory -> main() | func1 | fun2
پس از آنکه فراخوانی تابع func2 به پایان رسید این تابع از داخل حافظه Stack خارج شده و کنترل به تابع func1 باز می گردد و stack به صورت زیر در می آید:
Stack Memory -> main() | fun1
زمانی که فراخوانی تابع func1 به پایان برسد این تابع از حافظه stack خارج شده و کنترل به تابع main باز میگردد و این روند به همین صورت ادامه دارد تا زمانی که کار فراخوانی تابع main تمام شده و برنامه به اتمام رسد. با اتمام فراخوانی تابع main این تابع از از Stack خارج شده و کنترل به سیستم عامل باز می گردد. برای آشنایی بیشتر با ساختار Stack می توانید به یکی از کتابهای ساختمان داده ها مراجعه کنید.
حافظه Heap: حافظه Heap بخشی جداگانه از حافظه است که هیچ وابستگی به تابع های فراخوانی شده برنامه ندارد. متغیرها به صورت معمولی در این بخش قرار نمی گیرند و باید برنامه نویس به صورت دستی متغیرها رو داخل این بخش تعریف کند. به همین دلیل مدیریت حافظه Heap داخل برنامه باید به صورت دستی توسط برنامه نویس انجام شود. متغیرهای تعریف شده داخل حافظه Heap با اتمام فراخوانی یک تابع از بین نمی روند و تا زمانی که برنامه نویس خود این متغیر را از داخل حافظه Heap پاک نکند باقی خواهند ماند.
با تعاریفی که بالا شد، می توانیم به مبحث اختصاص داینامیک حافظه در C++ بپردازیم. در حالت عادی ما متعیر ها را به صورت زیر تعریف می کنیم:
با این تعریف متغیر myInt داخل حافظه Stack قرار میگیرد که در بالا خواص مربوط به حافظه Stack مطرح شدند. حال اگر ما بخواهیم این متغیر داخل حافظه Heap قرار بگیرد به چه صورت باید عمل کنیم؟ انجام این کار به راحتی و بوسیله دستور new امکان پذیر است.
برای اینکه بتوانیم متغییر myInt را که در بالا تعریف کردیم داخل حافظه Heap قرار دهیم از دستور زیر استفاده می کنیم:
int* myInt = new int(10);
همچنین دستور بالا را می توان به صورت زیر نیز نوشت:
int* myInt;
myInt = new int(10);
با نوشتن هر یک از دستورات بالا متغیر myInt به جای اینکه در حافظه Stack قرار بگیرد، در حافظه Heap فضایی برای آن اختصاص داده خواهد شد. اما نکته مهمی که باید به آن توجه شود این است که متغیر myInt در ابتدا به عنوان یک اشاره گر تعریف شده است. پس برای استفاده از مقدار آن باید از علامت * که به آن dereference نیز می گویند استفاده کرد. فرض کنیم که بخواهیم مقدار myInt را بر روی صفحه چاپ کنیم. باید به صورت زیر بنویسیم:
اما همانطور که در بالا اشاره شد، بعد از اختصاص حافظه داینامیک برای یک متغیر، خود برنامه نویس باید به صورت دستی این متغیر را از حافظه پاک کند. این کار با دستور delete امکان پذیر است. برای مثال، برای حذف متغیر myInt به صورت زیر می نویسیم:
اختصاص حافظه داینامیک برای آرایه ها: اما یکی از کاربرد های مهم اختصاص حافظه داینامیک استفاده از آرایه ها با طول متغیر در برنامه است. در حالت عادی وقتی بخواهیم آرایه ای را داخل برنامه تعریف کنیم کامپایلر باید از اندازه آن آرایه اطلاع داشته باشد تا فضای مورد نظر برای آرایه را داخل حافظه Stack در نظر بگیرد. اما شرایطی پیش می آید که می خواهیم اندازه آرایه را در حین اجرای برنامه تعیین کنیم. برای مثال اندازه آرایه را از کاربر گرفته و آرایه ای با اندازه مورد نظر ایجاد کنیم. در اینجا اختصاص حافظه داینامیک به کمک ما می آید. به مثال زیر توجه کنید:
int main()
{
int arraySize;
cout << "Enter array size: ";
cin >> arraySize;
int* myList = new int[arraySize];
delete [] myList;
return 0;
}
در قطعه کد بالا اندازه آرایه از کاربر پرسیده شده و آرایه ای با اندازه وارد شده ایجاد می شود. با هر بار اجرای برنامه کاربر می تواند یک سایز دلخواه را وارد کند و برنامه آرایه ای با اندازه وارد شده ایجاد کند. به دستوری که آرایه را از حافظه پاک می کند توجه کنید. در صورتی که متغیر تعریف شده آرایه باشد، برای پاک کردن آن از حافظه Heap حتما" باید بعد از دستور delete علامت [] قرار گیرد تا برنامه بداند که باید یک آرایه را از حافظه Heap پاک کند.
مطلب بعدی اختصاص حافظه داینامیک برای آرایه های چند بعدی است. اختصاص حافظه داینامیک برای آرایه های چند بعدی کمی پیچیده تر از اختصاص حافظه داینامیک برای آرایه های یکی بعدی است. برای اختصاص حافظه داینامیک آرایه چند بعدی باید هر یک از خانه های آرایه تعریف شده، خود نیز اشاره گر باشند. یعنی برای هر یک از خانه های آرایه دوباره تخصیص حافظه مجدد صورت گیرد. برای اینکه مسئله روشن تر شود به مثال زیر توجه کنید.
فرض کنید می خواهیم داخل یک برنامه آرایه ای دو بعدی ایجاد کنیم و این آرایه باید داخل حافظه Heap قرار گیرد. تعداد سطر ها و ستون ها در هنگام اجرای برنامه از کاربر پرسیده میشود و بر اساس مقادیر ورودی آرایه مورد نظر ایجاد می شود. قطعه کد زیر این کار را انجام میدهد. به نحوه تعریف متغیر myList در خط هفتم توجه کنید، این متغیر آرایه ای است که هر خانه از آن خود از نوع اشاره گر تعریف شده است:
int main()
{
int rows = 0;
int cols = 0;
cout << "rows: ";
cin >> rows;
cout << "cols: ";
cin >> cols;
int** myList = new int*[rows];
for(int i = 0; i < rows; i++)
myList[i] = new int[cols];
myList[1][2] = 7;
for( int i = 0 ; i < ROWS ; i++ )
delete [] dynamicArray[i] ;
delete [] dynamicArray;
return 0;
}
در خط های 1 تا 6 متغیرهای rows و cols تعریف شده اند و داخل آنها مقادیر مربوط به سطر و ستون که توسط کاربر وارد می شود قرار میگیرد. تعریف خط 7 بسیار مهم است که در بالا نیز در مورد آن توضیح داده شد. در این خط از برنامه تعداد سطر های آرایه تعریف می شوند که هر یک خود از نوع اشاره گر اند. در خط 8 و 9 با یک حلقه ستون های مربوط به آرایه دو بعدی ایجاد می شوند. برای مثال اگر کاربر مقدار 4 برای تعداد سطرها و تعداد 5 برای تعداد ستون ها را وارد کند در خط هفتم آرایه ای با 4 خانه ایجاد می شود که هر یک از خانه های آن خود تعریف یک اشاره گر از نوع int هستند و با دستورات خط های 8 و 9 برای هر یک از خانه های آرایه حافظه ای با تعداد خانه های cols اختصاص داده میشود. دستور خطوط 11، 12 و 13 برای پاک سازی حافظه بسیار مهم است و نباید فراموش شود. ابتدا آرایه های مربوط به هر یک از خانه های myList پاک شده و سپس خود آرایه myList از داخل حافظه پاک می شود.
اختصاص حافظه برای آرایه های بیشتر از 2 بعد پیچیده تر هستند.
یکی دیگر از کابردهای اختصاص حافظه داینامیک ایجاد آرایه هایی با تعداد خانه های بسیار بالا است. در حالت عادی اگر بخواهیم آرایه ای با تعداد خانه های بسیار بالا مثلا 500000 خانه ایجاد کنیم ممکن است با پیغام Stack Overflow مواجه شویم. در حالی که با اختصاص حافظه داینامیک برای یک آرایه همچین پیغامی دریافت نخواهیم کرد. البته این مورد کاربرد های زیادی ندارد و به ندرت استفاده می شود یا شاید به هیچ عنوان مورد استفاده واقع نشود.