مهندس موفق الکترونیک

تخصیص حافظه پویا در C با malloc ، calloc ، realloc و free

پیش از آموختن تخصیص حافظه پویا در C ، باید بدانیم که: مدیریت حافظه در C چگونه عمل می کند؟ و در ادامه با تخصیص حافظه پویا در C با malloc ، calloc ، realloc و free و آشنا می‌شویم.

مدیریت حافظه در C چگونه عمل می کند؟

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

برای مثال، یک متغیر float هنگام اعلان معمولاً 4 بایت فضا(متناسب با پلتفرم) اشغال می کند. با استفاده از عملگر sizeof می توان این موضوع را تایید کرد:

#include <stdio.h>

int main() { float x; printf("The size of float is %d bytes", sizeof(x)); return 0;}

خروجی برابر خواهد بود با:

The size of float is 4 bytes

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

#include <stdio.h>

int main() { float arr[10];

printf("The size of the float array with 10 element is %d", sizeof(arr)); return 0;}

نتیجه برابر است با:

The size of the float array with 10 element is 40

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

در این مبحث خواهیم آموخت:

  • مدیریت حافظه در زبان C چگونه عمل می کند؟
  • تخصیص حافظه پویا
  • تابع malloc
  • تابع free
  • تابع calloc
  • Calloc در مقایسه با malloc: تفاوت های کلیدی
  • تابع realloc
  • آرایه های پویا

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

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

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

کتابخانه <stdlib.h> توابعی برای مدیریت حافظه پویا دارد.

تخصیص حافظه پویا در C

حال اجازه دهید این توابع و کاربردشان را توضیح دهیم.

تابع malloc

تابع malloc() برای تخصیص حافظه بکار می رود. این تابع برای تخصیص بلوکی از حافظه به صورت پویا استفاده می شود. این تابع فضایی ازحافظه با اندازه مشخص را رزرو کرده و یک اشاره گر null که به مکان حافظه اشاره می کند را برمیگرداند. اشاره گر برگردانده شده معمولاً از نوع void است. این بدان معناست که تابع malloc را می توان به هرنوع اشاره گری اختصاص داد.

مطلب پیشنهادی:  زبان برنامه نویسی C چیست؟ معرفی، تاریخچه و مفاهیم اولیه

دستور زبان:

ptr = (cast_type *) malloc (byte_size);

در عبارت بالا:

  • Ptr اشاره گری از نوع cast_type است.
  • تابع malloc یک اشاره گر به حافظه تخصیص یافته با اندازه byte_size را برمی گرداند.

مثال:

Example: ptr = (int *) malloc (50)

اگر این عبارت بدون خطا اجرا شود، فضایی از حافظه به اندازه 50 بایت رزرو می شود. آدرس اولین بایت از فضای رزرو شده به اشاره گر ptr نوع int داده می شود.

مثال دیگری را ملاحظه کنید:

#include <stdlib.h>

int main(){

int *ptr;

ptr = malloc(15 * sizeof(*ptr)); /* a block of 15 integers */

if (ptr != NULL) {

*(ptr + 5) = 480; /* assign 480 to sixth integer */

printf("Value of the 6th integer is %d",*(ptr + 5));

}

}

خروجی:

Value of the 6th integer is 480

تخصیص حافظه پویا در C

دقت کنید که عبارت (sizeof(*ptr بجای (sizeof(int بکار برده شده تا هنگام تبدیل *ptr به نوع داده دیگر، کدمان قابل اعتماد باشد.

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

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

تابع malloc را می توان با نوع داده کارکتر و همچنین نوع داده های پیچیده مانند ساختارها هم بکار بست.

تابع free

حافظه متغیرها در زمان کامپایل به صورت خودکار بازستانده(deallocate) می شود. در تخصیص حافظه پویا، باید حافظه را به صورت صریح آزاد کرد.اگر این کار انجام نشود، ممکن است با خطای کمبود حافظه(out of memory error) مواجه شوید.

تابع ()free برای آزادسازی/بازستانی حافظه فراخوانی می شود. با آزادسازی حافظه در برنامه، فضای بیشتری برای استفاده در آینده فراهم می کنید.

برای مثال:

#include <stdio.h>

int main() {

int* ptr = malloc(10 * sizeof(*ptr));

if (ptr != NULL){

*(ptr + 2) = 50;

printf("Value of the 2nd integer is %d",*(ptr + 2));

}

free(ptr);

}

خروجی:

Value of the 2nd integer is 50

تابع calloc

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

مطلب پیشنهادی:  دستور Switch Case در زبان C همراه با مثال

Malloc برای تخصیص یک بلوک از فضای حافظه به کار می رفت، درحالی که callaloc برای تخصیص چندین بلوک از فضای حافظه به کار می رود. همه بلوک ها در تابع calloc اندازه یکسانی دارند.

دستور زبان:

ptr = (cast_type *) calloc (n, size);
  • عبارت بالا برای تخصیص n بلوک حافظه با اندازه یکسان بکار می رود.
  • بعد از اینکه فضای حافظه تخصیص یافت، همه بایت ها به صفر مقداردهی اولیه می شودند.
  • اشاره گری که به اولین بایت از حافظه اختصاص یافته اشاره دارد برگردانده می شود.

هرگاه فرآیند تخصیص حافظه با یک خطا، مثلاً کمبود حافظه، مواجه شود، اشاره گر null برگردانده می شود.

برنامه زیر حاصل جمع یک دنباله حسابی را محاسبه می کند:

#include <stdio.h>

int main() {

int i, * ptr, sum = 0;

ptr = calloc(10, sizeof(int));

if (ptr == NULL) {

printf("Error! memory not allocated.");

exit(0);

}

printf("Building and calculating the sequence sum of the first 10 terms \ n ");

for (i = 0; i < 10; ++i) { * (ptr + i) = i;

sum += * (ptr + i);

}

printf("Sum = %d", sum);

free(ptr);

return 0;

}

نتیجه:

Building and calculating the sequence sum of the first 10 terms

Sum = 45

Calloc در مقایسه با malloc تفاوت های کلیدی

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

بلوک تخصیص یافته با تابع calloc همواره به صفر مقداردهی اولیه می شود حال آنکه malloc هموراه حاوی یک مقدار زائد(garbage ) می باشد.

تابع realloc

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

از realloc همچنین می توان برای کاهش اندازه حافظه قبلاً تخصیص یافته هم استفاده کرد:

دستور زبان:

ptr = realloc (ptr,newsize);

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

مطلب پیشنهادی:  آموزش کار با CMake

برای مثال:

#include <stdio.h>

int main () {

char *ptr;

ptr = (char *) malloc(10);

strcpy(ptr, "Programming");

printf(" %s, Address = %u\n", ptr, ptr);

ptr = (char *) realloc(ptr, 20); //ptr is reallocated with new size

strcat(ptr, " In 'C'");

printf(" %s, Address = %u\n", ptr, ptr);

free(ptr);

return 0;

}

هروقت که realloc به یک عملیات ناموفق منجر شود، یک اشاره گر null برگردانده می شود و داده قبلی نیز آزاد می شود.

آرایه های پویا

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

در برنامه زیر، یک آرایه را به صورت پویا ساخته و اندازه آن را تغییر می دهیم:

#include <stdio.h>

int main() {

int * arr_dynamic = NULL;

int elements = 2, i;

arr_dynamic = calloc(elements, sizeof(int)); //Array with 2 integer blocks

for (i = 0; i < elements; i++) arr_dynamic[i] = i;

for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);

elements = 4;

arr_dynamic = realloc(arr_dynamic, elements * sizeof(int)); //reallocate 4 elements

printf("After realloc\n");

for (i = 2; i < elements; i++) arr_dynamic[i] = i;

for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);

free(arr_dynamic);

}

نتیجه:

arr_dynamic[0]=0

arr_dynamic[1]=1

After realloc

arr_dynamic[0]=0

arr_dynamic[1]=1

arr_dynamic[2]=2

arr_dynamic[3]=3

خلاصه

  • حافظه را می توان با ایجاد بلوک های مورد نیاز در هیپ، به صورت پویا مدیریت کرد.
  • در تخصیص حافظه به صورت پویا، حافظه در زمان اجرا تخصیص می یابد.
  • تخصیص حافظه پویا، پیاده سازی رشته ها و آرایه هایی را فراهم آورده که اندازه آنها انعطاف پذیر بوده و می توان در هر زمان دلخواه در برنامه آن را تغییر داد.
  • این نوع تخصیص ، در زمان‌هایی از میزان حافظه اشغالی توسط یک نوع ساختار خاص اطلاع ندارید به کار می آید.
  • Malloc یک تابع تخصیص حافظه پویا است. این تابع بلوک های حافظه با اندازه مشخص که به یک مقدار زائد مقداردهی اولیه شده اند را تخصیص می دهد.
  • Calloc یک تابع تخصیص حافظه پیوسته است که چندین بلوک حافظه را در آن واحد تخصیص داده و به 0 مقداردهی اولیه می کند.
  • Realloc حافظه را با اندازه مشخص مجدداً تخصیص می دهد.
  • تابع free برای آزادسازی حافظه پویای تخصیص یافته بکار می رود.

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

اگر این نوشته‌ برایتان مفید بود لطفا کامنت بنویسید.

مطالعه دیگر جلسات این آموزش<< جلسه قبلی                    جلسه بعدی >>

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

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

2 دیدگاه

  1. خیلی ممنون خدا خیرتون بده