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

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

    پشته

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

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

    پشته

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

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

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

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

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

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

    تخصیص (گرفتن حافظه) : void *malloc(size_t size);

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

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

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

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

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

اگر (p==NULL) خارج شود<< "\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); // free dyn. حافظه

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

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

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

f(50); //هایلایت دین. حافظه آزاد شود

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

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

f1 (0); // free dyn. حافظه

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

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 e-ths یعنی. هنگام استفاده از تابع جدیدبدون نیاز به ریختن اشاره گر و بدون نیاز به استفاده sizeof().آزاد کردن انتخاب با جدیدحافظه با تماس زیر مدیریت می شود: delete p; اگر نیاز به اختصاص حافظه برای یک عنصر دارید، می توانید از int * q = new int استفاده کنید. یا int * q = new int(10); // int اختصاص داده شده با مقدار 10 مقدار دهی اولیه می شود در این صورت حذف به این صورت خواهد بود: delete q;

آخرین به روز رسانی: 1396/05/28

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

اعداد دوگانه = (1.0، 2.0، 3.0، 4.0، 5.0)؛

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

برای مدیریت تخصیص حافظه پویا، از تعدادی توابع استفاده می شود که در فایل هدر stdlib.h تعریف شده است:

    malloc(). دارای نمونه اولیه

    Void *malloc(s unsigned);

    s بایت حافظه را اختصاص می دهد و یک اشاره گر به ابتدای حافظه اختصاص داده شده برمی گرداند. در صورت شکست، NULL را برمی‌گرداند

    calloc(). دارای نمونه اولیه

    Void *calloc(nunsigned n, unsigned m);

    حافظه را برای n عنصر از هر یک از m بایت اختصاص می دهد و یک اشاره گر را به ابتدای حافظه اختصاص داده شده برمی گرداند. در صورت شکست، NULL را برمی‌گرداند

    realloc(). دارای نمونه اولیه

    Void *realloc(void *bl, unsigned ns);

    اندازه بلوک حافظه تخصیص داده شده قبلی که با bl به آن اشاره شده است را به ns بایت تغییر می دهد. اگر اشاره گر bl NULL باشد، یعنی هیچ حافظه ای تخصیص داده نشده باشد، عملکرد تابع مشابه عملکرد malloc است.

    رایگان() . دارای نمونه اولیه

    void *free(void *bl);

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

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

کاربرد توابع را روی یک مسئله ساده در نظر بگیرید. طول آرایه ناشناخته است و در زمان اجرا توسط کاربر وارد می شود و همچنین مقادیر تمام عناصر توسط کاربر وارد می شود:

#عبارتند از #عبارتند از int main(void) ( int *block; // اشاره گر به بلوک حافظه int n؛ // تعداد عناصر آرایه // تعداد عناصر را وارد کنید printf("Size of array="); scanf("%d", &n); // تخصیص حافظه برای آرایه // تابع malloc یک اشاره گر از نوع void* // برمی گرداند که به طور خودکار به نوع int* block = malloc(n * sizeof(int)) تبدیل می شود؛ // اعداد را در آرایه برای(int i=0;i

خروجی کنسول برنامه:

اندازه آرایه=5 بلوک=23 بلوک=-4 بلوک=0 بلوک=17 بلوک=81 23 -4 0 17 81

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

ابتدا کاربر تعداد عناصری را که وارد متغیر n می شود را وارد می کند. پس از آن، باید برای این تعداد عنصر حافظه اختصاص دهید. برای تخصیص حافظه در اینجا، می‌توانیم از هر یک از سه تابع توضیح داده شده در بالا استفاده کنیم: malloc، calloc، realloc. اما به طور خاص در این شرایط از تابع malloc استفاده خواهیم کرد:

Block = malloc(n * sizeof(int));

اول از همه، لازم به ذکر است که هر سه تابع ذکر شده در بالا یک اشاره گر نوع void * را در نتیجه جهانی بودن مقدار بازگشتی برمی گرداند. اما در مورد ما آرایه ای از نوع int ایجاد می شود که توسط یک اشاره گر از نوع int * مدیریت می شود، بنابراین نتیجه تابع malloc به طور ضمنی به نوع int * فرستاده می شود.

به خود تابع malloc تعداد بایت ها برای بلوک اختصاص داده شده ارسال می شود. محاسبه این عدد بسیار ساده است: فقط تعداد عناصر را در اندازه یک عنصر ضرب کنید n * sizeof(int) .

پس از اتمام تمام اقدامات، حافظه با استفاده از تابع free() آزاد می شود:

رایگان (بلاک)؛

مهم این است که پس از اجرای این تابع، دیگر نمی توانیم از آرایه استفاده کنیم، به عنوان مثال، مقادیر آن را در کنسول چاپ کنیم:

رایگان (بلاک)؛ برای (int i=0;i

و اگر سعی کنیم این کار را انجام دهیم، مقادیر نامشخصی دریافت خواهیم کرد.

به جای تابع malloc، می‌توانیم به طور مشابه از تابع calloc() استفاده کنیم که تعداد عناصر و اندازه یک عنصر را می‌گیرد:

Block = calloc(n, sizeof(int));

یا می توانید از تابع realloc() نیز استفاده کنید:

int *block = NULL; block = realloc(block, n * sizeof(int));

هنگام استفاده از realloc، مطلوب است (در برخی از محیط ها، به عنوان مثال، در ویژوال استودیو، اجباری) به مقداردهی اولیه اشاره گر حداقل NULL.

اما به طور کلی، هر سه تماس در این مورد تأثیر مشابهی خواهند داشت:

Block = malloc(n * sizeof(int)); block = calloc(n, sizeof(int)); block = realloc(block, n * sizeof(int));

اکنون یک مسئله پیچیده تر را در نظر بگیرید - تخصیص حافظه پویا برای یک آرایه دو بعدی:

#عبارتند از #عبارتند از int main(void) ( int **table; // اشاره گر به یک بلوک از حافظه برای آرایه ای از اشاره گرها در داخل * ردیف ها؛ // اشاره گر به یک بلوک حافظه برای ذخیره اطلاعات روی ردیف ها int تعداد ردیف؛ // تعداد ردیف ها int d; // شماره ورودی / / تعداد ردیف ها را وارد کنید printf("Rows count="); scanf("%d", &rowscount); // حافظه را برای جدول آرایه دو بعدی تخصیص دهید = calloc(rowscount, sizeof( int*))؛ rows = malloc(sizeof(int)*rowscount)؛ // حلقه بین سطرها برای (int i = 0; i)

جدول متغیر یک اشاره گر به آرایه ای از اشاره گرها از نوع int* را نشان می دهد. هر اشاره گر جدول[i] در این آرایه نشانگر نشانگر زیرآرایه ای از عناصر int، یعنی ردیف های جداول جداگانه است. و متغیر جدول در واقع نشانگر یک نشانگر به آرایه ای از اشاره گرها به ردیف های جدول است.

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

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

جدول = calloc (شمار ​​ردیف، اندازه (int*)); rows = malloc(sizeof(int)*rowscount);

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

Scanf("%d"، & ردیف[i]); جدول[i] = calloc(ردیف[i]، sizeof(int));

سپس، عناصر برای هر ردیف وارد می شود.

در پایان خروجی برنامه، حافظه آزاد می شود. برنامه حافظه را برای ردیف های جدول اختصاص می دهد، بنابراین این حافظه باید آزاد شود:

رایگان (جدول[i])؛

و علاوه بر این، حافظه اختصاص داده شده برای نشانگرهای جدول و ردیف ها آزاد می شود:

رایگان (جدول)؛ رایگان (ردیف)؛

خروجی کنسول برنامه:

تعداد سطر=2 تعداد ستون ها برای 1=3 جدول=1 جدول=2 جدول=3 تعداد ستون ها برای 2=2 جدول=4 جدول=5 1 2 3 4 5

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

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

// مثال استفاده از عملیات new int *ptrvalue = new int; //که در آن ptrvalue یک اشاره گر به ناحیه حافظه اختصاص داده شده از نوع int //new عملیات تخصیص حافظه آزاد برای شی ایجاد شده است.

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

// نمونه ای از استفاده از عملیات حذف: delete ptrvalue; // که در آن ptrvalue یک اشاره گر به یک ناحیه حافظه اختصاص داده شده از نوع int // delete یک عملیات انتشار حافظه است.

بیایید برنامه ای ایجاد کنیم که یک متغیر پویا ایجاد کند.

// new_delete.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #include "stdafx.h" #include << "ptrvalue = "<< *ptrvalue << endl; حذف ptrvalue; // سیستم حافظه را آزاد کنید("pause"); return 0; }

// کد کد::Blocks

// کد Dev-C++

// new_delete.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #عبارتند از با استفاده از namespace std. int main(int argc, char* argv) ( int *ptrvalue = int جدید؛ // تخصیص پویا حافظه برای یک شی از نوع int *ptrvalue = 9؛ // مقدار دهی اولیه شی از طریق اشاره گر //int *ptrvalue = int جدید (9)؛ مقداردهی اولیه را می توان بلافاصله هنگام اعلام یک شی پویا انجام داد<< "ptrvalue = "<< *ptrvalue << endl; حذف ptrvalue; // انتشار حافظه بازگشت 0; )

(!LANG:In خط 10 نحوه اعلان و مقداردهی اولیه یک شی پویا را با 9 نشان می دهد، تنها چیزی که نیاز دارید این است که مقدار را در پرانتز بعد از نوع داده مشخص کنید. نتیجه برنامه در شکل 1 نشان داده شده است.

Ptrvalue = 9 برای ادامه هر کلیدی را فشار دهید. . .

شکل 1 - متغیر پویا

ایجاد آرایه های پویا

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

// اعلان یک آرایه پویا یک بعدی با 10 عنصر: float *ptrarray = new float ; // که در آن ptraray یک اشاره گر به ناحیه حافظه اختصاص داده شده برای آرایه ای از اعداد واقعی از نوع شناور است // در براکت های مربع اندازه آرایه را نشان می دهد.

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

// انتشار حافظه اختصاص داده شده برای یک آرایه پویا یک بعدی: حذف ptrarray;

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

// new_delete_array.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #عبارتند از"stdafx.h" #include !} // در فایل هدر // در فایل هدر < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

// کد کد::Blocks

// کد Dev-C++

// new_delete_array.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #عبارتند از // در فایل هدر شامل نمونه اولیه تابع time() #include است // در فایل هدر شامل نمونه اولیه تابع setprecision()#include است #عبارتند از با استفاده از namespace std. int main(int argc, char* argv) ( srand(time(0)); // تولید اعداد تصادفی float *ptrarray = float جدید؛ // ایجاد یک آرایه پویا ده عنصری از اعداد واقعی برای (int count = 0; شمردن< 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

آرایه پویا یک بعدی ایجاد شده با اعداد واقعی تصادفی به دست آمده با استفاده از توابع تولید اعداد تصادفی پر می شود و اعداد در محدوده 1 تا 10 تولید می شوند، فاصله به صورت زیر تنظیم می شود - rand() % 10 + 1 . برای بدست آوردن اعداد واقعی تصادفی، یک عملیات تقسیم با استفاده از یک ریخته گری صریح به نوع واقعی مخرج - float((rand() % 10 + 1)) انجام می شود. برای نشان دادن تنها دو رقم اعشار از تابع setprecision(2) استفاده کنید , نمونه اولیه این تابع در فایل هدر است . تابع time(0) مولد اعداد تصادفی را با یک مقدار موقت نشان می دهد، بنابراین، به نظر می رسد که تصادفی بودن وقوع اعداد را بازتولید می کند (شکل 2 را ببینید).

آرایه = 0.8 0.25 0.86 0.5 2.2 10 1.2 0.33 0.89 3.5 هر کلیدی را برای ادامه فشار دهید. . .

شکل 2 - آرایه پویا در C++

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

ما یاد گرفته ایم که چگونه آرایه های پویا یک بعدی را ایجاد کنیم و با آنها کار کنیم. حال بیایید به یک قطعه کد نگاه کنیم که نشان می دهد چگونه یک آرایه پویا دو بعدی اعلام می شود.

// اعلان یک آرایه پویا دو بعدی با 10 عنصر: float **ptrarray = new float* ; // دو رشته در آرایه برای (int count = 0; count< 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

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

// آزاد کردن حافظه اختصاص داده شده برای یک آرایه پویا دو بعدی: برای (int count = 0; count< 2; count++) delete ptrarray; // где 2 – количество строк в массиве

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

// new_delete_array2.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #include "stdafx.h" #include #عبارتند از #عبارتند از < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

// کد کد::Blocks

// کد Dev-C++

// new_delete_array2.cpp: نقطه ورودی برنامه کنسول را تعریف می کند. #عبارتند از #عبارتند از #عبارتند از #عبارتند از با استفاده از namespace std. int main(int argc, char* argv) ( srand(time(0)); // ایجاد اعداد تصادفی // ایجاد پویا یک آرایه دو بعدی ده عنصری از اعداد واقعی شناور **ptrarray = شناور جدید* ;/ / دو رشته در آرایه برای (int count = 0; count< 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

هنگام خروجی آرایه، از تابع setw() استفاده می شود، اگر فراموش نکرده باشید، یک مکان با اندازه معین را برای داده های خروجی اختصاص می دهد. در مورد ما، چهار موقعیت برای هر عنصر از آرایه وجود دارد، این به شما اجازه می دهد تا در ستون ها، اعداد با طول های مختلف را تراز کنید (شکل 3 را ببینید).

2.7 10 0.33 3 1.4 6 0.67 0.86 1.2 0.44 برای ادامه هر کلیدی را فشار دهید. . .

شکل 3 - آرایه پویا در C++

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

در این مقاله، من به چند تکنیک از این دست نگاه خواهم کرد. تفاوت مثال‌های مقاله (مثلاً با این یکی) در این است که عملگرهای new و delete بیش از حد بارگذاری می‌شوند و به همین دلیل، ساختارهای نحوی حداقلی و اصلاح برنامه ساده خواهد بود. دام‌هایی که در این فرآیند یافت می‌شوند نیز شرح داده شده‌اند (البته، استادانی که استاندارد را از روی جلد به جلد می‌خوانند، شگفت‌زده نخواهند شد).

0. آیا به کار دستی با حافظه نیاز داریم؟

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

بیایید تست های ساده ای برای C++ و C# بنویسیم (سی شارپ به دلیل مدیریت حافظه عالی خود که اشیا را به نسل ها تقسیم می کند، از استخرهای مختلف برای اشیا با اندازه های مختلف و غیره استفاده می کند، شناخته شده است).

گره کلاس ( public: Node* next; ); // ... برای (int i = 0; i< 10000000; i++) { Node* v = new Node(); }

کلاس Node ( عمومی Node next; ) // ... برای (int l = 0; l< 10000000; l++) { var v = new Node(); }

با وجود تمام "خلاء کروی" مثال، تفاوت زمانی 10 برابر شد (62 میلی ثانیه در مقابل 650 میلی ثانیه). علاوه بر این، مثال سی شارپ به پایان رسید و طبق قوانین خوش اخلاقی در c++، اشیاء انتخاب شده باید حذف شوند که باعث افزایش بیشتر شکاف (تا 2580 میلی ثانیه) می شود.

1. استخر اشیاء

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

از آنجایی که هدف این است که تا حد امکان کمتر برنامه را دستکاری کنید، تنها کاری که می توان انجام داد اضافه کردن یک میکس BlockAlloc به کلاس Node است:
گره کلاس: عمومی BlockAlloc

اول از همه، ما به مجموعه ای از بلوک های بزرگ (صفحات) نیاز داریم که از سیستم عامل یا C-runtime گرفته می شود. می توان آن را در بالای malloc و توابع رایگان سازماندهی کرد، اما برای کارایی بیشتر (برای رد شدن از یک لایه اضافی انتزاعی)، از VirtualAlloc/VirtualFree استفاده می کنیم. این توابع حافظه را در بلوک هایی که مضربی از 4K هستند تخصیص می دهند و همچنین فضای آدرس فرآیند را در بلوک هایی که مضرب 64K هستند ذخیره می کنند. با تعیین همزمان گزینه‌های commit و rezerv، از سطح دیگری از انتزاع، رزرو فضای آدرس و تخصیص صفحات حافظه در یک تماس صرف‌نظر می‌کنیم.

کلاس PagePool

inline size_t align(size_t x, size_t a) ( return ((x-1) | (a-1)) + 1; ) //#define align(x, a) ((((x)-1) | ( (الف)-1)) + 1) الگو کلاس PagePool ( public: void* GetPage() ( void* page = VirtualAlloc (NULL, PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); pages.push_back(page); بازگشت صفحه; ~PagePool() ( برای (بردار ::iterator i = pages.begin(); i != pages.end(); ++i) (VirtualFree(*i, 0, MEM_RELEASE);) ) خصوصی: برداری صفحات؛ )

سپس مجموعه ای از بلوک ها را با اندازه معین سازماندهی می کنیم

کلاس BlockPool

قالب کلاس BlockPool: PagePool ( عمومی: BlockPool() : head(NULL) (BlockSize = align(sizeof(T)، Alignment); count = PageSize / BlockSize; ) void* AllocBlock() ( // todo: lock(this) if (!head) FormatNewPage(); void* tmp = head; head = *(void**)head; return tmp; ) void FreeBlock(void* tmp) ( // todo: lock(this) *(void**)tmp = head; head = tmp؛ ) private: void* head؛ size_t BlockSize؛ size_t count؛ void FormatNewPage() ( void* tmp = GetPage(؛ head = tmp; for(size_t i = 0; i< count-1; i++) { void* next = (char*)tmp + BlockSize; *(void**)tmp = next; tmp = next; } *(void**)tmp = NULL; } };

اظهار نظر // todo: قفل کردن (این) مکان هایی که نیاز به همگام سازی بین رشته ای دارند علامت گذاری شده اند (مثلاً از EnterCriticalSection یا boost::mutex استفاده کنید).

اجازه دهید توضیح دهم که چرا هنگام "قالب بندی" صفحه، انتزاع FreeBlock برای افزودن یک بلوک به استخر استفاده نمی شود. اگر چیزی شبیه به آن نوشته می شد

برای (size_t i = 0; i< PageSize; i += BlockSize) FreeBlock((char*)tmp+i);

سپس صفحه مطابق با اصل FIFO "در معکوس" علامت گذاری می شود:

چندین بلوک درخواست شده از استخر در یک ردیف دارای آدرس های نزولی هستند. و پردازنده دوست ندارد به عقب برگردد، این کار Prefetch را می شکند ( UPD: برای پردازنده های مدرن مرتبط نیست). اگر نشانه گذاری را در یک حلقه انجام دهید
برای (size_t i = PageSize-(BlockSize-(PageSize%BlockSize)); i != 0; i -= BlockSize) FreeBlock...
سپس چرخه نشانه گذاری به آدرس ها برمی گردد.

اکنون که آماده سازی انجام شد، می توانیم کلاس mixin را توصیف کنیم.
قالب کلاس BlockAlloc ( public: static void* operator new(size_t s) ( if (s != sizeof(T)) ( return::operator new(s); ) return pool.AllocBlock(); ) static void operator delete(void * m, size_t s) ( if (s != sizeof(T)) ( :: حذف اپراتور(m); اضافه بارهای nothrow_t، طبق نظر borisko" // http://habrahabr.ru/post/148657/#comment_5020297 // از پنهان کردن مکان جدید که برای کانتینرهای stl نیاز است اجتناب کنید... static void* operator new(size_t, void * m) ( بازگشت m ; ) // ... و هشدار در مورد حذف مکان گم شده ... عملگر void static delete(void*, void*) ( ) private: static BlockPool استخر؛ ) قالب استخر بلوک BlockAlloc ::استخر؛

توضیح دهید که چرا بررسی ها لازم است if (s != sizeof(T))
چه زمانی کار می کنند؟ سپس، هنگامی که یک کلاس ایجاد/حذف می شود، از پایه T به ارث می رسد.
Decendants از new/delete معمولی استفاده می کنند، اما BlockAlloc نیز می تواند در آن مخلوط شود. بنابراین، ما می توانیم به راحتی و با خیال راحت تعیین کنیم که کدام کلاس ها باید از استخرها استفاده کنند، بدون ترس از شکستن چیزی در برنامه. وراثت چندگانه نیز با این میکسین عالی عمل می کند.

آماده. Node را از BlockAlloc به ارث می بریم و تست را دوباره اجرا می کنیم.
زمان تست هم اکنون 120 میلی ثانیه است. 5 برابر سریعتر اما در سی شارپ هنوز تخصیص دهنده بهتر است. احتمالاً فقط یک لیست پیوندی نیست. (اما اگر بلافاصله پس از جدید، بلافاصله حذف را فراخوانی کنید، و در نتیجه حافظه زیادی را هدر ندهید، داده ها را در حافظه نهان قرار دهید، 62 میلی ثانیه دریافت می کنیم. عجیب است. درست مانند NET CLR، گویی که متغیرهای محلی آزاد شده را برمی گرداند. بلافاصله به استخر مربوطه، بدون انتظار برای GC)

2. ظرف و محتویات رنگارنگ آن

چند بار با کلاس هایی مواجه می شوید که تعداد زیادی اشیاء مختلف کودک را در خود ذخیره می کنند، به طوری که طول عمر آنها بیشتر از عمر والدین نیست؟

به عنوان مثال، این می تواند یک کلاس XmlDocument پر از کلاس های Node و Attribute و همچنین رشته های c (char*) باشد که از متن داخل گره ها گرفته شده است. یا لیستی از فایل ها و دایرکتوری ها در فایل منیجر که در هنگام خواندن مجدد دایرکتوری یک بار بارگذاری می شوند و دیگر تغییر نمی کنند.

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

بیایید کلاس PointerBumpAllocator را ایجاد کنیم، که می‌تواند تکه‌هایی با اندازه‌های مختلف را از یک بلوک بزرگ جدا کند و وقتی بلوک قدیمی تمام شد، یک بلوک بزرگ جدید اختصاص دهد.

کلاس PointerBumpAllocator

قالب کلاس PointerBumpAllocator ( عمومی: PointerBumpAllocator() : free(0) () void* AllocBlock(size_t block) (// todo: lock(this) block = align(block, Alignment); if (block > free) ( free = align (block, PageSize); head = GetPage (رایگان)؛ ) void* tmp = head؛ head = (char*)head + block; free -= block; return tmp; ) ~PointerBumpAllocator() ( برای (بردار ::iterator i = pages.begin(); i != pages.end(); ++i) (VirtualFree(*i, 0, MEM_RELEASE); ) خصوصی: void* GetPage(size_t size) ( void* page = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); pages.push_back(page) ؛ صفحه برگشت؛ ) بردار صفحات؛ خالی*سر; size_t رایگان; ) typedef PointerBumpAllocator<>DefaultAlocator;

در نهایت، بیایید مخلوط ChildObject را با new overload شده توصیف کنیم و حذف کنیم که به تخصیص دهنده داده شده دسترسی دارد:

قالب struct ChildObject ( static void* operator new(size_t s, A& allocator) ( return allocator.AllocBlock(s); ) static void* operator new(size_t s, A* allocator) ( return allocator->AllocBlock(s); ) static عملگر void delete(void*, size_t) ( ) // *1 اپراتور void static delete(void*, A*) ( ) static void operator delete(void*, A&) ( ) private: static void* operator new(size_t s ));

در این حالت، علاوه بر افزودن یک میکس به کلاس فرزند، همچنین باید تمام تماس‌ها را به new ثابت کنید (یا از الگوی "factory" استفاده کنید). دستور عملگر جدید به صورت زیر خواهد بود:

جدید(...پارامترهای عملگر...) ChildObject(...پارامترهای سازنده...)

برای راحتی کار، دو عملگر جدید تعریف کرده ام که A& یا A* را می گیرند.
اگر تخصیص دهنده به عنوان عضو به کلاس والد اضافه شود، گزینه اول راحت تر است:
node = new(allocator) XmlNode(nodename);
اگر تخصیص دهنده به عنوان جد (ناخالصی) اضافه شود، دومی راحت تر است:
node = new(this) XmlNode(nodename);

دستور خاصی برای فراخوانی delete وجود ندارد، کامپایلر حذف استاندارد (با علامت *1) را بدون توجه به اینکه کدام عملگر جدید برای ایجاد شی استفاده شده است، فراخوانی می کند. یعنی دستور حذف نرمال است:
حذف گره؛

اگر یک استثنا در سازنده ChildObject (یا جانشین آن) رخ دهد، delete با امضایی فراخوانی می شود که با امضای عملگر جدید استفاده شده برای ایجاد این شی مطابقت دارد (اولین پارامتر size_t با void* جایگزین می شود).

قرار دادن اپراتور جدید در بخش خصوصی از فراخوانی جدید بدون تعیین تخصیص دهنده جلوگیری می کند.

من یک مثال کامل از استفاده از جفت Allocator-ChildObject می‌آورم:

مثال

کلاس XmlDocument: عمومی DefaultAllocator ( عمومی: ~XmlDocument() ( برای (بردار ::iterator i = nodes.begin(); i != nodes.end(); ++i) (حذف (*i)؛ )) void AddNode (محتوای char*، char* نام) (char* c = (char*) AllocBlock(strlen(content)+1)؛ strcpy(c، محتوا . ( public: XmlNode(char* _content, char* _name) : content(_content), name(_name) ( ) private: char* content; char* name; ); خصوصی: برداری گره ها )

نتیجه. مقاله 1.5 سال پیش برای سندباکس نوشته شده بود، اما افسوس که ناظم آن را دوست نداشت.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// فرض کنید ptr قبلاً با حذف جدید ptr اختصاص داده شده است. // حافظه اشاره شده با ptr را به سیستم عامل بازگردانید ptr = 0; // ptr را null کنید (از 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 حذف 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) تنظیم کنید، مگر اینکه بلافاصله پس از حذف از محدوده خارج شوند.

اپراتور جدید

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

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

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

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

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

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

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

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

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

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

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

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

if (ptr) delete ptr;

اگر (ptr)

حذف ptr

در عوض، شما فقط می توانید بنویسید:

حذف ptr

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

نشت حافظه

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

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