یک برنامه می تواند اطلاعات را به دو روش اصلی در حافظه اصلی کامپیوتر ذخیره کند. اولین مورد از متغیرهای سراسری و محلی، از جمله آرایه ها، ساختارها و کلاس ها استفاده می کند. در مورد متغیرهای محلی جهانی و ثابت، مکان ذخیره اطلاعات برای کل مدت اجرای برنامه ثابت است. در مورد متغیرهای محلی، حافظه روی پشته تخصیص داده می شود. اگرچه Borland C++ این متغیرها را بسیار کارآمد مدیریت می کند، اما استفاده از آنها مستلزم آن است که برنامه نویس از قبل میزان حافظه مورد نیاز در طول اجرای برنامه را بداند.

راه دوم برای ذخیره اطلاعات استفاده از سیستم تخصیص حافظه پویا Borland C++ است. در این روش، حافظه برای ذخیره اطلاعات در صورت نیاز از یک ناحیه حافظه آزاد تخصیص داده می شود و برگردانده می شود، یعنی. زمانی که نیاز به آن ناپدید شد، آزاد می شود. ناحیه حافظه آزاد بین ناحیه حافظه ای که برنامه در آن قرار دارد و پشته قرار دارد. این ناحیه Heap نامیده می شود و برای درخواست های تخصیص حافظه پویا استفاده می شود.

مزیت استفاده از حافظه پویا این است که می توان از یک حافظه برای ذخیره اطلاعات مختلف در طول اجرای برنامه استفاده کرد. از آنجایی که حافظه برای یک هدف خاص تخصیص داده می شود و پس از اتمام استفاده از آن آزاد می شود، همان حافظه را می توان در زمان دیگری برای اهداف دیگر در قسمت دیگری از برنامه استفاده کرد. یکی دیگر از مزایای تخصیص حافظه پویا این است که می توان از آن برای ایجاد لیست های پیوندی، درختان باینری و سایر ساختارهای داده پویا استفاده کرد.

هسته تخصیص حافظه پویا C توابع malloc() و free() است که بخشی از کتابخانه استاندارد هستند. هرگاه درخواست تخصیص حافظه توسط تابع malloc() انجام شود، بخشی از حافظه آزاد موجود تخصیص داده می شود. هر زمان که این حافظه با استفاده از تابع ()free آزاد شود، این حافظه به سیستم برمی گردد.

زبان C++ دو عملگر را برای تخصیص حافظه پویا تعریف می کند - new و delete.

استاندارد ANSI C تنها چهار تابع تخصیص حافظه پویا را تعریف می کند: calloc()، malloc()، free() و realloc(). با این حال، Borland C++ شامل چندین تابع تخصیص حافظه پویا دیگر است. هنگام کامپایل کد برای مدل حافظه 32 بیتی مدرن، حافظه مسطح است و معمولاً فقط از چهار تابع تخصیص حافظه استاندارد استفاده می شود.

استاندارد ANSI C مشخص می کند که اطلاعات هدر مورد نیاز برای تخصیص حافظه پویا در فایل stdlib.h موجود است. با این حال، Borland C++ اجازه استفاده از فایل های هدر stdlib.h یا alloc.h را می دهد. در اینجا ما از فایل هدر stdlib.h استفاده می کنیم زیرا قابلیت حمل را فراهم می کند. برخی دیگر از توابع تخصیص حافظه پویا به فایل‌های هدر alloc.h، malloc.h یا dos.h نیاز دارند. باید توجه ویژه ای داشته باشید که برای استفاده از هر تابع به کدام فایل هدر نیاز است.

ما امکانات تخصیص حافظه پویا را کشف کردیم. چه مفهومی داره؟ این بدان معناست که با تخصیص حافظه پویا، حافظه نه در مرحله کامپایل، بلکه در مرحله اجرای برنامه ذخیره می شود. و این به ما توانایی تخصیص حافظه کارآمدتر، عمدتاً برای آرایه ها را می دهد. با تخصیص حافظه پویا، ما نیازی به تنظیم اندازه آرایه از قبل نداریم، به خصوص که همیشه مشخص نیست که آرایه باید چه اندازه باشد. در مرحله بعد، بیایید نحوه تخصیص حافظه را بررسی کنیم.

تخصیص حافظه در C (تابع malloc)

تابع malloc() در تعریف شده است فایل هدر stdlib.h، برای مقداردهی اولیه اشاره گرها با مقدار حافظه مورد نیاز استفاده می شود. حافظه از یک بخش تخصیص داده می شود حافظه دسترسی تصادفیبرای هر برنامه ای که روی این دستگاه اجرا می شود قابل دسترسی است. آرگومان تعداد بایت های حافظه ای است که باید تخصیص داده شود؛ تابع یک اشاره گر را به بلوک تخصیص یافته در حافظه برمی گرداند. تابع malloc () درست مانند هر تابع دیگری کار می کند، چیز جدیدی نیست.

زیرا انواع مختلفداده ها نیازهای حافظه متفاوتی دارند، ما به نوعی باید یاد بگیریم که چگونه اندازه داده ها را بر حسب بایت بدست آوریم انواع متفاوت. به عنوان مثال، ما به یک بخش از حافظه برای آرایه ای از مقادیر از نوع int نیاز داریم - این یک اندازه حافظه است، و اگر نیاز به تخصیص حافظه برای آرایه ای با همان اندازه، اما از نوع char داشته باشیم - این یک اندازه است. اندازه های مختلف بنابراین، شما باید به نوعی اندازه حافظه را محاسبه کنید. این را می توان با استفاده از عملیات sizeof() انجام داد که یک عبارت را می گیرد و اندازه آن را برمی گرداند. برای مثال sizeof(int) تعداد بایت های مورد نیاز برای ذخیره مقدار int را برمی گرداند. بیایید به یک مثال نگاه کنیم:

#عبارتند از int *ptrVar = malloc(sizeof(int));

در این مثال، در خط 3اشاره گر ptrVar یک آدرس به یک مکان حافظه اختصاص داده می شود که اندازه آن با نوع داده int مطابقت دارد. به طور خودکار، این منطقه حافظه برای برنامه های دیگر غیر قابل دسترس می شود. این بدان معنی است که پس از غیر ضروری شدن حافظه اختصاص داده شده، باید به صراحت آزاد شود. اگر حافظه به صراحت آزاد نشود، پس از اتمام برنامه، حافظه برای سیستم عامل، به این نشت حافظه می گویند. شما همچنین می توانید اندازه حافظه اختصاص داده شده را که باید با ارسال یک نشانگر تهی تخصیص داده شود تعیین کنید، در اینجا یک مثال آورده شده است:

Int *ptrVar = malloc(sizeof(*ptrVar));

اینجا چه خبره؟ عملیات sizeof(*ptrVar) اندازه مکان حافظه ارجاع شده توسط اشاره گر را تخمین می زند. از آنجایی که ptrVar یک اشاره گر به یک مکان حافظه int است، sizeof() اندازه عدد صحیح را برمی گرداند. یعنی در واقع بر اساس قسمت اول تعریف اشاره گر، اندازه قسمت دوم محاسبه می شود. پس چرا ما به این نیاز داریم؟ این ممکن است در صورتی ضروری باشد که به طور ناگهانی نیاز داشته باشیم که تعریف یک اشاره گر را تغییر دهیم، مثلا int را به شناور تغییر دهیم و سپس نیازی به تغییر نوع داده در دو قسمت از تعریف اشاره گر نداشته باشیم. اگر قسمت اول را تغییر دهیم کافی است:

Float *ptrVar = malloc(sizeof(*ptrVar));

همانطور که می بینید، در چنین رکوردی یک بسیار وجود دارد نقطه قوت، ما نباید تابع malloc() را با استفاده از sizeof(float) فراخوانی کنیم. در عوض، یک اشاره گر به نوع float به malloc() دادیم، در این صورت اندازه حافظه اختصاص داده شده خود به خود مشخص می شود!

این به ویژه در صورتی مفید است که نیاز به تخصیص حافظه به دور از تعریف اشاره گر داشته باشید:

شناور *ptrVar; /*. . . صد خط کد */ . . . ptrVar = malloc(sizeof(*ptrVar));

اگر قرار بود از ساختار تخصیص حافظه با عملیات sizeof() استفاده کنید، باید تعریف اشاره گر را در کد پیدا کنید، به نوع داده آن نگاه کنید و تنها در این صورت می توانید حافظه را به درستی تخصیص دهید.

آزاد کردن حافظه اختصاص داده شده

آزادسازی حافظه با استفاده از تابع ()free انجام می شود. در اینجا یک مثال است:

رایگان (ptrVar)؛

پس از آزاد کردن حافظه، تمرین خوب است که نشانگر را صفر کنید، یعنی *ptrVar = 0 را اختصاص دهید. اگر 0 را به یک اشاره گر اختصاص دهید، نشانگر پوچ می شود، به عبارت دیگر، دیگر به چیزی اشاره نمی کند. همیشه بعد از آزاد کردن حافظه به نشانگر 0 اختصاص دهید، در غیر این صورت، حتی پس از آزاد کردن حافظه، نشانگر همچنان به آن اشاره می کند، یعنی می توانید به طور تصادفی به برنامه های دیگری که ممکن است از این حافظه استفاده می کنند آسیب برسانید، اما حتی چیزی در مورد آن نمی دانید. آن را خواهید فهمید و فکر می کنید که برنامه به درستی کار می کند.

P.S.: هر کسی که علاقه مند به ویرایش ویدیو باشد ممکن است به این ویرایشگر ویدیوی ویندوز 7 علاقه مند شود. ویرایشگر ویدیو Movavi نام دارد، شاید کسی قبلاً با آن آشنا باشد یا حتی با آن کار کرده باشد. با استفاده از این برنامه به زبان روسی، می توانید به راحتی ویدیو را از دوربین اضافه کنید، کیفیت و پوشش را بهبود بخشید ویدیوهای زیبااثرات

حافظه ایستاحتی قبل از شروع اجرای برنامه، در مرحله کامپایل و مونتاژ اختصاص داده می شود. متغیرهای استاتیک یک آدرس ثابت دارند که قبل از راه اندازی برنامه شناخته شده است و در طول عملیات آن تغییر نمی کند. متغیرهای استاتیک قبل از ورود ایجاد و مقداردهی اولیه می شوند عملکرد اصلی، که اجرای برنامه از آن شروع می شود.

دو نوع متغیر استاتیک وجود دارد:

  • متغیرهای جهانیمتغیرهایی تعریف شده اند خارج از کارکردکه در توضیحات آن کلمه ایستا وجود ندارد . معمولا توضیحاتمتغیرهای سراسری که شامل کلمه extern هستند در فایل های هدر (h-files) قرار می گیرند. کلمه extern به این معنی است که متغیر اعلام شده است، اما در این نقطه از برنامه ایجاد نشده است. تعاریفمتغیرهای سراسری، یعنی توضیحات بدون کلمه extern در فایل های پیاده سازی (c-files یا cpp-files) قرار می گیرند. مثال: متغیر سراسری maxid دو بار اعلام می شود:
    • در فایل h با استفاده از خط

      extern int maxind;

      این اعلان وجود چنین متغیری را گزارش می کند، اما آن متغیر را ایجاد نمی کند!
    • در فایل cpp با استفاده از خط

      int maxind = 1000;

      این توضیحات ایجاد می کندمتغیر maxind و مقدار اولیه 1000 را به آن اختصاص می دهد. توجه داشته باشید که استاندارد زبان نیازی به تخصیص اجباری مقادیر اولیه به متغیرهای جهانی ندارد، اما، با این وجود، بهتر است همیشه این کار را انجام دهید، در غیر این صورت متغیر حاوی یک مقدار غیرقابل پیش بینی خواهد بود (زباله، همانطور که برنامه نویسان می گویند). این سبک خوب است که همه متغیرهای سراسری را زمانی که تعریف می‌شوند مقداردهی اولیه کنید.
    متغیرهای سراسری به این دلیل نامیده می شوند که در هر جایی از برنامه در تمام فایل های آن موجود هستند. بنابراین، نام متغیرهای سراسری باید به اندازه کافی طولانی باشد تا از نام تصادفی دو متغیر مختلف جلوگیری شود. به عنوان مثال، نام های x یا n برای یک متغیر سراسری مناسب نیستند.
  • متغیرهای استاتیک- اینها متغیرهایی هستند که توضیحات آنها حاوی کلمه static است. به طور معمول، متغیرهای استاتیک توصیف می شوند خارج از کارکرد. چنین متغیرهای ایستا از هر نظر شبیه به متغیرهای سراسری هستند، با یک استثنا: دامنه یک متغیر استاتیک محدود به فایلی است که در آن تعریف شده است - و علاوه بر این، تنها پس از اعلام آن می توان از آن استفاده کرد، یعنی. زیر در متن به همین دلیل معمولاً اعلان متغیرهای استاتیک در ابتدای فایل قرار می گیرد. بر خلاف متغیرهای سراسری، متغیرهای ایستا هرگزدر فایل های h توضیح داده نمی شوند (اصلاح کننده های خارجی و استاتیک با یکدیگر در تضاد هستند). نکته: اگر می‌خواهید فقط برای توابع توضیح داده شده در داخل قابل دسترسی باشد، از متغیرهای ثابت استفاده کنید همان فایل. در صورت امکان، از متغیرهای سراسری در چنین شرایطی استفاده نکنید، این کار از تداخل نامگذاری در هنگام اجرای پروژه های بزرگ متشکل از صدها فایل جلوگیری می کند.
    • یک متغیر استاتیک را نیز می توان در داخل یک تابع توصیف کرد، اگرچه معمولاً هیچ کس این کار را انجام نمی دهد. متغیر در پشته قرار ندارد، اما در حافظه استاتیک، یعنی. نمی توان از آن در بازگشت استفاده کرد و مقدار آن بین ورودی های مختلف تابع ذخیره می شود. دامنه چنین متغیری محدود به بدنه تابعی است که در آن تعریف شده است. در غیر این صورت شبیه یک متغیر ثابت یا سراسری است. توجه کنید که کلمه کلیدی static در C برای دو هدف مختلف استفاده می شود:
      • به عنوان نشانه ای از نوع حافظه: متغیر در حافظه ثابت قرار دارد، نه در پشته.
      • به عنوان راهی برای محدود کردن دامنه یک متغیر به یک فایل (در مورد توصیف یک متغیر خارج از یک تابع).
  • کلمه static نیز می تواند در هدر تابع ظاهر شود. با این حال، فقط برای محدود کردن دامنه نام تابع به یک فایل استفاده می شود. مثال:

    static int gcd(int x, int y); // نمونه اولیه تابع. . . static int gcd(int x, int y) ( // پیاده سازی... )

    نکته: استفاده کنید اصلاح کننده استاتیکدر هدر تابع اگر معلوم باشد که تابع فقط در یک فایل فراخوانی می شود. کلمه static باید هم در توضیحات نمونه اولیه تابع و هم در هدر تابع هنگام اجرای آن ظاهر شود.

پشته یا حافظه محلی

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

متغیرهای محلی را می توان در بازگشت استفاده کرد زیرا وقتی یک تابع دوباره وارد می شود، مجموعه جدیدی از متغیرهای محلی بدون از بین بردن مجموعه قبلی در پشته ایجاد می شود. به همین دلیل، متغیرهای محلی در برنامه نویسی موازی از نظر رشته ای ایمن هستند (به بخش 2.6.2 مراجعه کنید). برنامه نویسان به این ویژگی یک تابع می گویند قابلیت ورود مجدد، از انگلیسی re-enter able - توانایی برای ورود مجدد. این یک کیفیت بسیار مهم از نظر قابلیت اطمینان و ایمنی برنامه است! برنامه ای که با متغیرهای استاتیک کار می کند این ویژگی را ندارد، بنابراین برای محافظت از متغیرهای استاتیک باید از مکانیسم های هماهنگ سازی(نگاه کنید به 2.6.2)، و منطق برنامه به طور چشمگیری پیچیده تر می شود. زمانی که می توانید از متغیرهای محلی استفاده کنید، همیشه باید از استفاده از متغیرهای سراسری و ثابت خودداری کنید.

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

حافظه پویا یا پشته

علاوه بر حافظه ثابت و پشته ای، یک منبع حافظه عملا نامحدود نیز وجود دارد که به آن می گویند پویا، یا یک دسته(پشته). این برنامه می تواند مناطقی از حافظه پویا را ضبط کند اندازه مناسب. پس از استفاده، ناحیه ای که قبلاً از حافظه پویا گرفته شده است باید آزاد شود.

فضا برای حافظه پویا اختصاص داده شده است حافظه مجازیپردازش بین حافظه ثابت و پشته. (مکانیسم حافظه مجازی در بخش 2.6 مورد بحث قرار گرفت.) به طور معمول، پشته در بالاترین آدرس های حافظه مجازی قرار دارد و به سمت آدرس های کوچکتر رشد می کند (به بخش 2.3 مراجعه کنید). برنامه و داده های ثابت در آدرس های پایین و متغیرهای استاتیک بالاتر قرار دارند. فضای بالای متغیرهای استاتیک و زیر پشته توسط حافظه پویا اشغال شده است:

نشانی محتویات حافظه

کد و داده های برنامه،

ضد دستکاری

...

متغیرهای استاتیک

برنامه ها

حافظه پویا

حداکثر آدرس (2 32 -4)

پشته

ساختار حافظه پویا به طور خودکار توسط سیستم زمان اجرا زبان C یا C++ حفظ می شود. حافظه پویا شامل بخش‌های آزاد و ضبط‌شده است که قبل از هر کدام یک توصیف‌گر بخش وجود دارد. هنگام اجرای درخواست ضبط حافظه، سیستم اجرا به دنبال یک قطعه آزاد با اندازه کافی می گردد و قطعه ای از طول مورد نیاز را در آن می گیرد. هنگامی که یک بخش حافظه آزاد می شود، به عنوان آزاد علامت گذاری می شود؛ در صورت لزوم، چندین بخش آزاد متوالی ادغام می شوند.

در زبان C، توابع استاندارد malloc و free برای به دست آوردن و آزاد کردن حافظه پویا استفاده می شود؛ توضیحات نمونه های اولیه آنها در فایل هدر استاندارد "stdlib.h" موجود است. (نام malloc مخفف است تخصیص حافظه- "ضبط حافظه".) نمونه های اولیه این توابع به شرح زیر است:

void *malloc(size_t n); // ضبط یک منطقه حافظه // از n بایت در اندازه void free(void *p); // بخشی از حافظه // را با آدرس p آزاد کنید

در اینجا n اندازه منطقه گرفته شده در بایت است، size_t نام یکی از انواع عدد صحیح است که تعریف می کند. حداکثر اندازهمنطقه تصرف شده نوع size_t در فایل هدر استاندارد "stdlib.h" با استفاده از عملگر typedef مشخص شده است (صفحه 117 را ببینید). این امر استقلال متن برنامه C را از معماری استفاده شده تضمین می کند. در معماری 32 بیتی، نوع size_t به عنوان یک عدد صحیح بدون علامت تعریف می شود:

typedef بدون علامت int size_t;

تابع malloc آدرس محل حافظه تخصیص داده شده را برمی گرداند، یا در صورت خرابی، صفر را برمی گرداند (زمانی که مکان آزاد به اندازه کافی بزرگ وجود ندارد). تابع رایگان یک قطعه از حافظه را با یک آدرس داده شده آزاد می کند. برای تنظیم آدرس از یک اشاره گر استفاده می شود نوع عمومیخالی* . پس از فراخوانی تابع malloc، باید با استفاده از عملیات type cast به یک اشاره گر به یک نوع خاص فرستاده شود، بخش 3.4.11 را ببینید. به عنوان مثال، مثال زیر یک تکه 4000 بایتی از حافظه پشته را می گیرد و آدرس آن را به یک اشاره گر به آرایه ای از 1000 عدد صحیح اختصاص می دهد:

int *a; // اشاره گر به آرایه ای از اعداد صحیح. . . a = (int *) malloc(1000 * sizeof(int));

عبارت در آرگومان تابع malloc 4000 است زیرا اندازه عدد صحیح sizeof(int) چهار بایت است. برای تبدیل یک اشاره گر، یک عملیات ریخته گری (int *) از یک اشاره گر نوع عمومی به یک اشاره گر به یک عدد صحیح استفاده می شود.

مثال: چاپ n عدد اول اول

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

از آنجایی که تعداد مورد نیاز اعداد اول n قبل از شروع برنامه ناشناخته است، ایجاد یک آرایه برای ذخیره آنها در حافظه استاتیک غیرممکن است. راه حل این است که پس از وارد کردن عدد n برای آرایه در حافظه پویا فاصله بگیرید. اینم متن کامل برنامه:

#عبارتند از #عبارتند از #عبارتند از int main() ( int n; // تعداد مورد نیاز اعداد اول int k؛ // تعداد فعلی اعداد اول یافت شده int *a؛ // اشاره گر به آرایه ای از اعداد اول یافت شده int p؛ // عدد بعدی برای بررسی int r؛ / / قسمت صحیح جذر p int i؛ // نمایه مقسوم علیه اول bool اول؛ // علامت اول printf("تعداد اعداد اول را وارد کنید:"); scanf("%d"، &n)؛ if (n<= 0) // Некорректное значение =>بازگشت 1; // با یک کد خطا خارج شوید // حافظه را برای آرایه ای از اعداد اول بگیرید a = (int *) malloc(n * sizeof(int)); a = 2; k = 1; // اضافه کردن دو به آرایه printf("%d ", a); // و آن را چاپ کنید p = 3; در حالی که (ک< n) { // Проверяем число p на простоту r = (int)(// Целая часть корня sqrt((double) p) + 0.001); i = 0; prime = true; while (i < k && a[i] <= r) { if (p % a[i] == 0) { // p делится на a[i] prime = false; // =>p اول نیست، break; // خروج از حلقه ) ++i; // به مقسوم علیه اول بعدی ) if (اول) ( // اگر عدد اول پیدا کنیم، a[k] = p؛ // سپس آن را به آرایه ++k اضافه کنیم؛ // تعداد اعداد اول را افزایش دهیم printf( "%d ", p ؛ // چاپ یک عدد اول اگر (k % 5 == 0) ( // رفتن به خط جدید printf("\n"); // بعد از هر پنج عدد ) ) p += 2; // به عدد فرد بعدی ) if (k % 5 != 0) ( printf("\n"); // Translate the line ) // Free up dynamic memory free(a); بازگشت 0; )

نمونه ای از نحوه کار این برنامه:

تعداد اعداد اول را وارد کنید: 50 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 1013131319 15 1 157 163 167 173 179 181 191 193 197 199 211 223 227 229

C++ اپراتورهای جدید و حذف

++C از اپراتورهای جدید و حذف برای به دست آوردن و آزاد کردن حافظه پویا استفاده می کند. آنها بر خلاف malloc و توابع آزاد که بخشی از کتابخانه توابع استاندارد C هستند، بخشی از زبان C++ هستند.

فرض کنید T نوعی از زبان C یا C++ باشد، p نشانگر یک شی از نوع T است. سپس از عملگر جدید برای گرفتن حافظه یک عنصر از نوع T استفاده می شود:

T*p; p = T جدید;

به عنوان مثال، برای گرفتن هشت بایت در زیر عدد واقعیقطعه دو نوع استفاده می شود

double *p; p = دو برابر جدید;

هنگام استفاده از new، بر خلاف malloc، نیازی به فرستادن اشاره گر از نوع void* به نوع دلخواه نیست: عملگر new یک اشاره گر را به نوع نوشته شده بعد از کلمه new برمی گرداند. دو قطعه معادل را در C و C++ مقایسه کنید.

    متغیرها و ثابت های جهانی را ذخیره می کند.

    اندازه در تدوین تعیین می شود.

    پشته

    متغیرهای محلی، آرگومان های تابع، و مقادیر محاسباتی میانی را ذخیره می کند.

    اندازه با شروع برنامه تعیین می شود (معمولاً 4 مگابایت اختصاص داده می شود).

    پشته

    حافظه تخصیص یافته پویا؛

    سیستم عامل حافظه را در قسمت هایی (در صورت نیاز) اختصاص می دهد.

اگر از قبل (در زمان نوشتن برنامه) ندانیم به چه مقدار حافظه نیاز داریم (مثلاً اندازه آرایه بستگی به آنچه کاربر در حین اجرای برنامه وارد می کند) باید از حافظه اختصاص داده شده به صورت پویا استفاده شود. هنگام کار با حجم زیاد داده

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

کار با حافظه پویا در c

برای کار با حافظه پویاتوابع زیر در زبان C استفاده می شود: malloc، calloc، رایگان، realloc. بیایید نگاهی دقیق تر به آنها بیندازیم.

    تخصیص (ضبط حافظه): void *malloc(size_t size);

مانند پارامتر ورودیاین تابع به اندازه حافظه ای که باید تخصیص داده شود می گیرد. مقدار برگشتی یک اشاره گر به حافظه تخصیص داده شده روی پشته است. اگر سیستم عامل قادر به تخصیص حافظه نبود (مثلاً حافظه کافی وجود نداشت)، malloc 0 را برمی گرداند.

    پس از پایان کار با حافظه اختصاص داده شده به صورت پویا، باید آن را آزاد کنید. برای این منظور استفاده می شود عملکرد رایگان، که حافظه را به کنترل سیستم عامل باز می گرداند: void free(void *ptr);

اگر حافظه پویا قبل از پایان برنامه آزاد نشود، پس از پایان برنامه به طور خودکار آزاد می شود. با این حال، این نشانه ای از سبک برنامه نویسی خوب است که به طور صریح حافظه را آزاد کنید که غیر ضروری شده است.

مثال:// تخصیص حافظه برای 1000 عنصر از نوع int

int * p = (int *) malloc(1000*sizeof(int));

if (p==NULL) cout<< "\n память не выделена";

آزاد (p)؛ // حافظه را به پشته برگردانید

2. تخصیص (ضبط حافظه): void *calloc(size_t nmemb, size_t size);

این تابع مشابه malloc کار می کند، اما در نحو متفاوت است (به جای اندازه حافظه اختصاص داده شده، باید تعداد عناصر و اندازه یک عنصر را مشخص کنید) و از این نظر که حافظه اختصاص داده شده به صفر خواهد رسید. برای مثال، پس از اجرای int * p = (int *) calloc(1000, sizeof(int)) p به ابتدای یک آرایه int متشکل از 1000 عنصر اولیه به صفر اشاره می کند.

3. تغییر اندازه حافظه: void *realloc(void *ptr, size_t size);

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

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

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

(int* p= (int *) calloc(n, sizeof(int))

آزاد (p)؛ // دین را رها کن. حافظه

    اشاره گر به عنوان یک شی حافظه ثابت محلی تعریف می شود. حافظه دینامیکی که یک بار در یک بلوک تخصیص داده می شود، هر بار که بلوک دوباره وارد می شود، از طریق یک اشاره گر قابل دسترسی است. حافظه فقط زمانی باید آزاد شود که استفاده از آن تمام شود.

(static int* p = (int *) calloc(n, sizeof(int));

p= (int *) calloc(n, sizeof(int));

f(50); //انتخاب دین. حافظه و سپس آزادسازی

f1 (100); //انتخاب دین. حافظه (اول دسترسی)

f1 (100); //با دین کار کن. حافظه

f1 (0); // دین را رها کن. حافظه

    اشاره گر یک شی سراسری نسبت به بلوک است. حافظه پویا در تمام بلوک هایی که اشاره گر "قابل مشاهده" است موجود است. حافظه فقط زمانی باید آزاد شود که استفاده از آن تمام شود

int*pG; //نشانگر کاری برای دین. حافظه (متغیر جهانی)

void init (اندازه int)

برای (i=0; i< size; i++) //цикл ввода чисел

( printf("x[%d]=",i);

scanf("%d"، &pG[i]);

جمع int (اندازه اینت)

برای (i=0; i< size; i++) //цикл суммирования

// تخصیص حافظه

pG= (int *) calloc(n, sizeof(int));

//با حافظه پویا کار کنید

printf(\ns=%d\n",sum(n));

رایگان (pG)؛ pG=NULL; // آزاد کردن حافظه

کار با حافظه پویا در سی پلاس پلاس

C++ مکانیسم خاص خود را برای تخصیص و آزادسازی حافظه دارد - اینها توابع هستند جدیدو حذف.مثال استفاده جدید: int * p = new int; // تخصیص حافظه برای 1000 واحد یعنی. هنگام استفاده از تابع جدیدبدون نیاز به ریختن اشاره گر و بدون نیاز به استفاده sizeof().آزادسازی تخصیص با استفاده از جدیدحافظه از طریق تماس زیر انجام می شود: حذف p; اگر نیاز به اختصاص حافظه برای یک عنصر دارید، می توانید از int * q = new int استفاده کنید. یا int * q = new int(10); // int اختصاص داده شده با مقدار 10 در این مورد مقداردهی اولیه می شود، حذف به این صورت خواهد بود: delete q;

C++ از سه نوع اصلی پشتیبانی می کند تخلیه(یا بیشتر "توزیع") حافظه، که ما قبلاً با دو مورد از آنها آشنا هستیم:

تخصیص حافظه استاتیکبرای و متغیرها وجود دارد. هنگام شروع برنامه، حافظه یک بار تخصیص داده می شود و در کل برنامه حفظ می شود.

تخصیص خودکار حافظهنگه می دارد برای و . هنگام ورود به بلوک حاوی این متغیرها، حافظه تخصیص داده می شود و هنگام خروج از آن حذف می شود.

تخصیص حافظه پویاموضوع این درس است.

تخصیص متغیر پویا

تخصیص حافظه استاتیک و خودکار دو ویژگی مشترک دارند:

تخصیص حافظه پویا چگونه کار می کند؟

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

هنگامی که به صورت پویا حافظه را تخصیص می دهید، از سیستم عامل می خواهید که مقداری از آن حافظه را برای استفاده توسط برنامه شما ذخیره کند. اگر سیستم عامل بتواند این درخواست را برآورده کند، آدرس این حافظه به برنامه شما برگردانده می شود. از این پس برنامه شما می تواند هر زمان که بخواهد از این حافظه استفاده کند. هنگامی که شما قبلاً تمام کارهای لازم را با این حافظه انجام داده اید، باید آن را به سیستم عامل برگردانید تا بین سایر درخواست ها توزیع شود.

بر خلاف تخصیص حافظه استاتیک یا خودکار، برنامه مسئول درخواست و برگرداندن حافظه تخصیص یافته به صورت پویا است.

آزاد کردن حافظه

هنگامی که یک متغیر را به صورت پویا تخصیص می دهید، می توانید آن را از طریق یا مقدار دهی اولیه یکنواخت (در C++11) مقداردهی اولیه کنید:

int *ptr1 = new int (7); // استفاده از مقدار دهی اولیه int *ptr2 = new int ( 8 ); // از مقدار دهی اولیه یکنواخت استفاده کنید

وقتی همه چیزهایی که نیاز بود قبلاً با یک متغیر تخصیص یافته به صورت پویا انجام شده است، باید صریحاً به C++ بگویید این حافظه را آزاد کند. برای متغیرها این کار با استفاده از اپراتور حذف:

// فرض کنید که ptr قبلاً با استفاده از عملگر new delete ptr تخصیص داده شده است. // حافظه اشاره شده با ptr را به سیستم عامل بازگردانید ptr = 0; // ptr را یک نشانگر تهی کنید (از nullptr به جای 0 در C++11 استفاده کنید)

عملگر حذف در واقع چیزی را حذف نمی کند. به سادگی حافظه ای را که قبلا تخصیص داده شده بود به سیستم عامل برمی گرداند. سپس سیستم عامل می تواند این حافظه را به برنامه دیگری (یا دوباره همان برنامه) اختصاص دهد.

اگرچه ممکن است به نظر برسد که ما در حال حذف هستیم متغیر، اما این درست نیست! یک متغیر اشاره گر همچنان همان محدوده قبلی را دارد و می توان مانند هر متغیر دیگری مقدار جدیدی به آن اختصاص داد.

توجه داشته باشید که حذف یک اشاره گر که به حافظه اختصاص داده شده به صورت پویا اشاره نمی کند می تواند مشکلاتی ایجاد کند.

تابلوهای آویزان

C++ هیچ تضمینی در مورد اینکه چه اتفاقی برای محتویات حافظه آزاد شده یا مقدار نشانگر حذف شده خواهد افتاد، نمی دهد. در بیشتر موارد، حافظه ای که به سیستم عامل بازگردانده می شود دارای همان مقادیری است که قبلا داشت. رهایی، و نشانگر تنها به حافظه آزاد شده (حذف شده) اشاره می کند.

اشاره گر به حافظه آزاد شده نامیده می شود علامت آویزان. عدم ارجاع یا حذف یک نشانگر آویزان نتایج غیرمنتظره ای ایجاد می کند. برنامه زیر را در نظر بگیرید:

#عبارتند از int main() ( int *ptr = new int؛ *ptr = 8؛ // مقدار را در محل حافظه اختصاص داده شده قرار دهید delete ptr؛ // حافظه را به سیستم عامل برگردانید. ptr اکنون یک نشانگر آویزان است std:: کوت<< *ptr; // разыменование висячего указателя приведёт к неожиданным результатам delete ptr; // попытка освободить память снова приведёт к неожиданным результатам также return 0; }

#عبارتند از

int main()

int * ptr = int جدید ; // به صورت پویا یک متغیر عدد صحیح را اختصاص دهید

* ptr = 8 ; // مقدار را در سلول حافظه اختصاص داده شده قرار دهید

حذف ptr // حافظه را به سیستم عامل برگردانید. ptr اکنون یک اشاره گر آویزان است

std::cout<< * ptr ; // عدم ارجاع به یک اشاره گر آویزان منجر به نتایج غیرمنتظره می شود

حذف ptr // تلاش برای آزادسازی مجدد حافظه منجر به نتایج غیرمنتظره ای نیز می شود

بازگشت 0 ;

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

فرآیند آزادسازی حافظه نیز می تواند به ایجاد منجر شود چندیننشانه های آویزان به مثال زیر توجه کنید:

#عبارتند از int main() ( int *ptr = int جدید؛ // به صورت پویا یک متغیر عدد صحیح را اختصاص می دهد int *otherPtr = ptr؛ // otherPtr اکنون به همان حافظه اختصاص داده شده اشاره می کند که ptr delete ptr؛ // حافظه را به سیستم عامل باز می گرداند. ptr و otherPtr اکنون نشانگرهای آویزان هستند ptr = 0؛ // ptr اکنون nullptr است // با این حال، otherPtr هنوز یک نشانگر آویزان است! بازگشت 0؛ )

#عبارتند از

int main()

int * ptr = int جدید ; // به صورت پویا یک متغیر عدد صحیح را اختصاص دهید

int * otherPtr = ptr ; // otherPtr اکنون به همان حافظه اختصاص داده شده به عنوان ptr اشاره می کند

حذف ptr // حافظه را به سیستم عامل برگردانید. ptr و otherPtr اکنون نشانگرهای آویزان هستند

ptr = 0 ; // ptr اکنون nullptr است

// با این حال، otherPtr هنوز یک اشاره گر آویزان است!

بازگشت 0 ;

ابتدا سعی کنید از موقعیت هایی که چندین اشاره گر به یک قسمت از حافظه اختصاص داده شده اشاره می کنند اجتناب کنید. اگر این امکان پذیر نیست، مشخص کنید که کدام اشاره گر «مالک» حافظه است (و مسئول حذف آن است) و کدام اشاره گر به سادگی به آن دسترسی دارند.

ثانیاً، هنگامی که یک اشاره گر را حذف می کنید، و اگر بلافاصله پس از حذف خارج نشد، باید آن را نول کنید، یعنی. مقدار 0 (یا در C++11) را اختصاص دهید. منظور ما از "خارج شدن از محدوده بلافاصله پس از حذف" این است که شما نشانگر را در انتهای بلوکی که در آن اعلام شده است حذف می کنید.

قانون: نشانگرهای حذف شده را روی 0 (یا nullptr در C++11) تنظیم کنید، مگر اینکه بلافاصله پس از حذف از محدوده خارج شوند.

اپراتور جدید

هنگام درخواست حافظه از سیستم عامل، در موارد نادر ممکن است در دسترس نباشد (یعنی در دسترس نباشد).

به طور پیش فرض، اگر اپراتور جدید کار نمی کرد، حافظه تخصیص داده نمی شد استثنا bad_alloc. اگر این استثنا به درستی مدیریت نشود (و چنین خواهد شد، زیرا ما هنوز به استثناها و نحوه رسیدگی به آنها نگاه نکرده ایم)، برنامه به سادگی با یک خطای استثنای کنترل نشده اجرا را متوقف می کند (خراش می شود).

در بسیاری از موارد، فرآیند پرتاب یک استثنا با عملگر جدید (و همچنین از کار افتادن برنامه) نامطلوب است، بنابراین یک شکل جایگزین از عملگر جدید وجود دارد که در صورت عدم تخصیص حافظه، یک اشاره گر تهی را برمی گرداند. فقط باید اضافه کنید std ثابت::nothrowبین کلمه کلیدی جدید و نوع داده:

int *value = new (std::nothrow) int; // اگر تخصیص پویا یک متغیر عدد صحیح با شکست مواجه شود، نشانگر مقدار تهی می شود

در مثال بالا، اگر new یک اشاره گر با حافظه تخصیصی پویا برگرداند، یک اشاره گر تهی برگردانده می شود.

عدم ارجاع آن نیز توصیه نمی شود، زیرا منجر به نتایج غیرمنتظره می شود (به احتمال زیاد خرابی برنامه). بنابراین، بهترین روش این است که تمام درخواست‌های تخصیص حافظه را بررسی کنید تا مطمئن شوید که درخواست‌ها با موفقیت تکمیل شده و حافظه تخصیص داده شده است:

int *value = new (std::nothrow) int; // درخواست تخصیص حافظه پویا برای یک مقدار صحیح در صورتی که (!value) // زمانی که new null برمی‌گرداند (یعنی حافظه تخصیص داده نشده است) مورد را رسیدگی کند ( // Handle this case std::cout<< "Could not allocate memory"; }

از آنجایی که عدم تخصیص حافظه توسط اپراتور جدید بسیار نادر است، معمولا برنامه نویسان انجام این بررسی را فراموش می کنند!

اشاره گرهای تهی و تخصیص حافظه پویا

نشانگرهای تهی (نشانگرهایی با مقدار 0 یا nullptr) به ویژه در فرآیند تخصیص حافظه پویا مفید هستند. حضور آنها به گونه ای که به ما می گوید: "هیچ حافظه ای به این اشاره گر اختصاص داده نشده است." و این به نوبه خود می تواند برای انجام تخصیص حافظه شرطی استفاده شود:

// اگر ptr هنوز به حافظه اختصاص داده نشده است، آن را تخصیص دهید اگر (!ptr) ptr = new int;

حذف نشانگر تهی تأثیری ندارد. بنابراین موارد زیر ضروری نیست:

if (ptr) delete ptr;

if (ptr)

حذف ptr

در عوض می توانید به سادگی بنویسید:

حذف ptr

اگر ptr تهی نباشد، متغیر تخصیص یافته به صورت پویا حذف خواهد شد. اگر مقدار اشاره گر صفر باشد، هیچ اتفاقی نمی افتد.

نشت حافظه

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

void doSomething() (int *ptr = new int;)