Press "Enter" to skip to content

مدیریت دستی حافظه در ++C/C

مدیریت دستی حافظه (manual memory allocation) یکی از امکانات قدرتمند، ولی در عین حال به شدت مستعد خطا، در ++C/C است. برای درک بهتر پیچیدگی‌های مدیریت حافظه در این زبان‌های برنامه‌نویسی نیاز است که بخش‌های حافظه و نحوه ذخیره سازی در این بخش‌ها شناخته شوند.

انواع مختلف حافظه در یک سیستم کامپیوتری

نوع حافظهناپایدار؟محلکاربرد
رجیسترهاRegistersبلهCPUلحظه پردازش
کش (L1/L2/L3)Cache (L1/L2/L3)بلهبین CPU و رمبالابردن دسترسی به رم
رم (پشته، هیپ)RAM (Stack, Heap)بلهحافظه سیستماجرای برنامه‌ها
رام\فلشROM/Flashخیرچیپ‌ست مادربوردفریمور (Firmware)، بوت‌لودر (Bootloader)
دیسک\SSDDisk/SSDخیردستگاه‌های ذخیره‌سازیذخیره فایل‌ها، سواپ، فایل‌های اجرایی
حافظه مجازیVirtual Memoryخیردیسکبرای ذخیره سرریز رم

بخش‌های مهم رم

  1. پشته (Stack)

    • برای ذخیره متغیرهای محلی، پارامترهای توابع و آدرس‌های بازگشت.
    • ویژگی‌ها: به صورت خودکار با فراخوانی و بازگشت توابع بزرگ و کوچک می‌شود؛دسترسی سریع؛ اندازه محدود.
    • مدیریت توسط: کامپایلر
  2. هیپ (Heap)

    • حافظه‌ای که به صورت داینامیک تخصیص داده می‌شود. (مثلا با malloc یا new)
    • ویژگی‌ها: به صورت دستی مدیریت می‌شود؛ از پشته بزرگتر ولی از آن کوچکتر است؛ احتمال وجود پراکندگی.
    • مدیریت توسط: برنامه‌نویس یا جمع‌آوری زباله (garbage collector).
  3. بخش داده (Data Segment)

    • برای ذخیره متغیرهای سراسری (global) و ایستا (static)، صرف نظر از اینکه مقدار اولیه داشته باشد یا نه.
    • ویژگی‌ها: در کل مدت اجرای بنامه باقی می‌ماند
    • مدیریت توسط: کامپایلر یا لودر سیستم عامل
  4. بخش متن (Text Segment یا Code Segment)

    • برای ذخیره کد کامپایل شده برنامه (دستورات ماشین).
    • ویژگی‌ها: معمولا فقط خواندنی است؛ بین فرایندها به اشتراک گذاشته می‌شود.

در برنامه‌نویسی ++C/C، پشته (Stack) و هیپ (Heap) دو بخش مهم از حافظه هستند که برای اهداف مختلفی استفاده می‌شوند. درک تفاوت بین آنها برای مدیریت بهینه منابع و جلوگیری از خطاهای رایج، حیاتی است.

پشته و هیپ

هم پشته و هم هیپ بخش‌هایی از حافظه هستند که در کد شما قابل استفاده هستند و هنگام اجرای برنامه در اختیار برنامه قرار می‌گیرند، ولی با هم تفاوت‌های اساسی دارند. در پشته مقادیر به ترتیبی که دریافت می‌شود، ذخیره و در جهت عکس پاک می‌کند. به این رفتار اصطلاحا «آخرین ورودی، اولین خروجی» (last in, first out) یا LIFO گفته می‌شود. برای مثال چیدن بشقاب‌ها روی هم را در نظر بگیرید: وقتی بشقاب جدید اضافه می‌شود، آنها را بالای بقیه قرار می‌دهید، و وقتی که بشقاب لازم دارید از بالای پشته بشقاب‌ها برمی‌دارید. تمام داده‌هایی که در پشته ذخیره می‌شوند باید مقدار مشخص و ثابت داشته باشند.

در مقابل داده‌هایی که هنگام کامپایل کردن اندازه مشخصی یا ثابتی ندارد در هیپ ذخیره می‌شوند. ساختار هیپ نامنظم‌تر از پشته است: هنگامی که داده‌ای جدید در هیپ وارد می‌کنید، در واقع دارید درخواست مقداری از حافظه را ثبت می‌کنید. تخصیص‌دهنده حافظه (memory allocator) بخشی از حافظه را که اندازه مناسب دارد پیدا و علامت‌گذاری می‌کند و در نهایت یک اشاره‌گر (pointer) را، که در واقع آدرس آن محل در حافظه است،  برمی‌گرداند. به این پروسه تخصیص دادن حافظه در هیپ یا به طور خلاصه تخصیص حافظه گفته می‌شود. از آنجایی که اشاره‌گر مقدار مشخص و ثابتی دارد می‌توان آن را در پشته ذخیره کرد، اما هنگامی که مقادیر اصلی را لازم دارید باید اشاره‌گر را دنبال کنید تا آن را از روی هیپ بخوانید.

پشته (Stack)

ویژگی‌ها:

  • مدیریت خودکار: حافظه پشته به صورت خودکار توسط کامپایلر مدیریت می‌شود.

  • سرعت بالا: تخصیص و آزادسازی حافظه بسیار سریع است.

  • اندازه ثابت: اندازه پشته معمولاً محدود است (مثلاً چند مگابایت) و توسط سیستم عامل تعیین می‌شود.

  • زندگی کوتاه: متغیرهای پشته با خروج از بلوک یا تابع، به طور خودکار حذف می‌شوند.

  • موارد استفاده: ذخیره متغیرهای محلی، آرگومان‌های توابع و آدرس بازگشت فراخوانی‌ها.

مثال استفاده از پشته: 

				
					#include <iostream>
using namespace std;

void func() {
    int x = 10; // متغیر x در پشته ذخیره میشود
    cout << x << endl;
} // با پایان تابع، x بهطور خودکار حذف میشود

int main() {
    func();
    return 0;
}
				
			

مشکلات احتمالی:

  • سرریز پشته (Stack Overflow): اگر حافظه پشته پر شود (مثلاً با فراخوانی بازگشتی بیش از حد)، برنامه کرش می‌کند.

				
					void recursiveFunc() {
    int data[1000]; // مصرف حافظه پشته
    recursiveFunc(); // فراخوانی بازگشتی بیپایان
}
				
			

هیپ (Heap)

ویژگی‌ها:

  • مدیریت دستی: توسعه‌دهنده باید حافظه را صریحاً با new/malloc اختصاص و با delete/free آزاد کند.

  • اندازه پویا: هیپ حافظه بسیار بزرگتری دارد و به صورت پویا رشد می‌کند.

  • زندگی طولانی: داده‌ها تا زمانی که توسعه‌دهنده آنها را آزاد نکند، در حافظه باقی می‌مانند.

  • سرعت پایینتر: تخصیص و آزادسازی حافظه نسبت به پشته کندتر است.

  • موارد استفاده: ساختارهای داده پویا (مثل آرایه‌ها یا اشیاء با اندازه نامعلوم).

مثال استفاده از هیپ:

				
					#include <iostream>
using namespace std;

int main() {
    int* ptr = new int(20); // اختصاص حافظه در هیپ
    cout << *ptr << endl;
    delete ptr; // آزادسازی حافظه (ضروری!)
    return 0;
}
				
			

مشکلات احتمالی:

  • نشت حافظه (Memory Leak): اگر حافظه هیپ را آزاد نکنید: 

				
					int main() {
    int* ptr = new int(20);
    // delete فراموش شده! حافظه هرگز آزاد نمیشود.
    return 0;
}
				
			
  • دسترسی به حافظه آزادشده (Dangling Pointer):
				
					int main() {
    int* ptr = new int(20);
    delete ptr;
    cout << *ptr; // خطا: دسترسی به حافظه آزادشده!
    return 0;
}
				
			
ویژگی پشته (Stack) هیپ (Heap)
مدیریت حافظه خودکار (کامپایلر) دستی (توسعه‌دهنده)
سرعت بسیار سریع کندتر
اندازه محدود (مثلاً ۸ مگابایت) بزرگ (تا حد حافظه فیزیکی)
زندگی متغیرها تا پایان بلوک/تابع تا زمان فراخوانی delete/free
امنیت کم‌خطر (خودکار) پرخطر (احتمال نشت یا دسترسی غیرمجاز)
کاربرد متغیرهای کوتاه‌عمر دادههای پویا با عمر طولانی

بهترین روش‌های استفاده

برای پشته:

  • تا حد امکان از متغیرهای محلی استفاده کنید.
  • از ذخیره دادههای حجیم (مثل آرایه‌های بزرگ) در پشته خودداری کنید.

برای هیپ:

  • همیشه حافظه اختصاص داده شده را آزاد کنید.
  • در C از malloc و free و در ++C از new و delete استفاده کنید.
  • در ++C از اشاره‌گرهای هوشمند (unique_ptrshared_ptr) استفاده کنید تا نشت حافظه کاهش یابد:
				
					#include <memory>
int main() {
    auto ptr = make_unique<int>(30); // حافظه بهصورت خودکار آزاد میشود
    cout << *ptr << endl;
    return 0;
}
				
			

 مثال ترکیبی (پشته + هیپ)

				
					#include <iostream>
using namespace std;

int main() {
    // پشته: آرایه کوچک
    int stackArray[5] = {1, 2, 3, 4, 5};

    // هیپ: آرایه پویا
    int* heapArray = new int[5];
    for (int i = 0; i < 5; i++) {
        heapArray[i] = i + 1;
    }

    delete[] heapArray; // آزادسازی حافظه هیپ
    return 0;
}
				
			

اولین نظر را ثبت کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *