شما اینجا هستید

خانه

آموزش ++C - اشاره گر (Pointer) - قسمت اول

در بسیاری از زبان های برنامه نویسی سطح بالا مثل C# یا Java یا حتی Visual basic شما اثری از اشاره گرها نمی بینید. خیلی از علاقه مندانی که شروع  به یادگیری زبان C می کنند، بزرگترین مشکلشان درک نحوه استفاده و کاربرد اشاره گر هاست. در این قسمت قصد دارم که مفهوم  اشاره گر و کاربرد های آن را توضیح بدهم.

وقتی شما برنامه ای می نویسید که قابلیت اجرا دارد ( به طور دقیق تر وقتی فایل خروجی پروژه EXE باشد) سیستم عامل هنگام اجرا فضایی از مموری (Memory) سیستم را به آن اختصاص می دهد. در واقع 2 نوع حافظه را به فایل اجرایی شما اختصاص می دهد. اولین نوع حافظه Stack نام دارد که معمولا بسیار محدود می باشد مثلا در حد چند کیلو بایت یا نهایتا چند MB. نوع دوم حافظه که Heap نام دارد به صورت دینامیک است و زمان اجرای برنامه قابل دسترسی است. حافظه Stack در زمان اجرای برنامه قابل افزایش نیست. یعنی در زمان کامپایل برنامه و ترجمه به اسمبلی باید دقیقا معلوم باشد. ولی حافظه heap هر زمان که لازم باشد در دسترس برنامه است. همچنین حافظه stack سرعت بالاتری نسبت به Heap دارد.

حالا سعی می کنم مفاهیم بالا را با استفاده از کد های C++ یا C به شما منتقل کنم:

وقتی شما مثلا داخل یک تابع (Function) یک متغیر محلی (local variable)  تعریف می کنید، به اندازه ی حجم آن متغیر از stack حافظه رزرو کرده اید. مثالا:

void main()

{

double x;

float y;

x = 12.56534;

 

const int valid_array_size = 100;

int invalid_array_size = 100;

int trueArray[valid_array_size];

int wrongArray[invalid_array_size]; // compile error

 

}

در مثال بالا همه متغیر های تعریف شده مقل  x و y و valid_array_size و ...  متغیرهای محلی هستندکه با خارج شدن از بلوک ( تابع) main به طور خودکار از حافظه stack حذف می شوند. حتی آرایه تعریف شده در این بلوک یعنی trueArray هم با خارج شدن از این تابع حذف می شود. به این نکته توجه داشته باشید که شما نمی توانید یک آرایه با اندازه متغیر در فضای stack بگیرید بنا بر این تعریف آرایه wrongArray حتما به شما در زمان کامپایل پیغام خطا خواهد داد. دلیل این مسئله همان طور که گفتم به این خاطر است که در حافظه stack نمی توان متغیر با اندازه متغیر گرفت. از حافظه استک استفاده های خاصی می شود، مثلا متغیر های محلی که در بالا مشاهده کردید و همچنین خود کامپایلر برای انتقال آرگومان های تابع و ....

در مورد کلاس ها و اشیا تعریف شده هم هیچ فرقی در این مورد وجود ندارد. مثالا :

MyCar pride;

CircleShape shape2;

مثال های  بالا نیز به اندازه سایز اشیا خود، حافظه stack اشغال می کنند.

توجه داشته باشید که ممکن است شما یک متغیر را به صورت Global بگیرید. در این صورت باز هم متغیر در حافظه stack خواهد بود و تا زمانی که برنامه بسته نشود، پاک نخواهد شد. ( شما امکانی برای حذف آن از حافظه ندارید.)

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

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

int a;

این یعنی سیستم عامل یک متغیر از نوع int با اندازه 4 بایت از حافظه رزرو کند. سیستم عامل بر اساس مکانیزم خاص خود یک فضای خالی 4 بایتی پیدا کرده و به شما می دهد. فرض کنید مثالا آدرس اولین خانه از این 4 بایت، شماره 67887672 باشد. در این صورت ادرس خانه دوم یکی بیشتر است یعنی :  67887673 و همین طور خانه های بعدی.  در نتیجه خانه 5 ام که 67887676 است، در زبان C یا C++ قابل دسترس است! و شما می توانید این خانه را هم مقدار دهی کنید. ولی از آن جایی که سیستم عامل از خانه 67887676 به بعد را برای شما رزرو نکرده است، هیچ تضمینی وجود ندارد که برنامه دیگری، process دیگری یا حتی برنامه خود شما ان را مقدار دهی نکند! در نتیجه نمی توان از متغیری که رزرو نشده استفاده کرد. نکته قابل توجه دیگر این است که ادرس های خانه های در حافظه در مبنای 16 است ( Hexadecimal). مثالا در یک اجرای نمونه من آدرس 0024F97C برای این متغیر در نظر گرفته شد.( بدیهی است  که با اجرای دوباره برنامه ممکن است که یک آدرس دیگر به این متغیر داده شود)

پس وقتی شما متغیر a را از حافظه می گیرید، در واقع a نامی است که شما هنگام برنامه نویسی از آن استفاده می کنید ولی حقیقتا این متغیر خانه مثلا شماره 0024F97C حافظه است. وقتی می نویسید a=56723  ( که در مبنای 16 می شود: DD93) یعنی خانه شماره  0024F97C مقدارش 0x93 و خانه شماره 0024F97D مقدارش 0xdd و 2 خانه ی بعدی مقدارشان 0x00 هستند.

یادتان باشد که برای این که این مفهوم  در ذهنتان خوب جای بگیرد همیشه تجسم کنید که بلوک های حافظه 1 بایتی هستند و هر خانه ای یک شماره منحصر به فرد به نام "آدرس" دارد، به این شکل:

0024F97C 0x93
0024F97D 0xdd ..... 

 

حالا با این توضیح می رویم سراغ کد نویسی! 

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

و ثانیا آدرس در زبان برنامه نویسی همان اشاره گر ( Pointer) است.

چند تا Operator یا عملگر در زبان C برای کار کردن با آدرس ها یا اشاره گر ها وجود دارد که ما در این جا مطرح می کنیم:

  • عملگر & : اگر این عملگر را پشت یک متغیر قرار دهید به شما آدرس آن متغیر را می دهد. مثلا :

int a = 56723;

cout << &a;

اگر کد بالا را اجرا کنید، در خروجی آدرس a چاپ می شود.( مثلا 0024F97C). البته توجه کنید که این آدرس به اولین خانه از 4 خانه 1 بایتی حافظه a اشاره دارد، چون حافظه a یک int است و 4 بایت است.

 

  • عملگر * type  : فرض کنید شما می خواهید یک آدرس از خانه های حافظه را در یک متغیر نگه دارید. برای این کار شما باید با استفاده از type * آن متغیر را تعریف کنید. توجه کنید که اندازه آدرس های یک سیستم عامل 32 بیتی برای همه ی انواع متغیر ها 4 بایت هستند. به مثال زیر توجه کنید:

 

int *     a_Pointer;

double*    xPtr;

MyObject*    objPointer;

//  .....

در کد های بالا a_Pointer یک متغیر از نوع آدرس int است. یعنی می تواند آدرس یک متغیر از نوع int را در خود نگه دارد.  همچنین xPtr یک متغیر از نوع آدرس double است. یعنی می تواند آدرس یک متغیر از نوع double را در خود نگه دارد.

و همجنینobjPointer  یک متغیر از نوع آدرس MyObject است. یعنی می تواند آدرس یک متغیر از نوع MyObject را در خود نگه دارد.

برای مثال فرض کنید بخواهیم آدرس خانه a  را در متغیر a_Pointer ذخیره کنیم، در این صورت باید بنویسیم:

a_Pointer = &a;

 

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

int b;

b = *a_Pointer;

cout << b; // result: 56723

توجه کنید که مفهوم ندارد که عملگر * را پشت یک متغیر ( غیر آدرسی مثلا a ) قرار دهیم چون در این صورت به صورت عملگر ضرب تلقی خواهد شد.

 

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

جواب این سوال را بعدا مفصل تر بحث خواهیم کرد. ولی به طور خلاصه می شود آدرس را بدون نوع آن ذخیره کرد.

 

؟؟؟ سوال بعدی که مطرح است این است که چرا زمانی که a_Pointer  * را چاپ می کنیم مقدار کل حافظه 4 بایتی یعنی 56723 چاپ می شود؟ مگر آدرس 0024F97C به اولین خانه 1 بایتی اشاره ندارد؟

نکته  مهم  این است که زمانی که شما یک آدرس از نوع متغیر معلوم مثلا int دارید ( یعنی *  int ) ؛ وقتی  عملگر * را پشت آدرس قرار می دهید، با اندازه سایز int مقدار را از حافظه می گیرد. در نتیجه اگر شما یک متغیر 8 بایتی مثل double هم داشته باشید،

xPtr * به شما عین 8 بایت را از حافظه می دهد. یعنی یکی از مزیت های مشخص بودن نوع اشاره گر همین است که به اندازه سایز متغیر مقادیر را از حافظه می دهد.( بدیهی است که سایز خود اشاره گر ها هیچ فرقی با هم ندارند.)

 

؟؟؟ چگونه می توان به دومین بایت از 4 بایت آدرس a_Pointer دسترسی داشت؟

 

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

پاسخ به سوالات بالا و مباحث پیشرفته تر و مثال های کاربردی تری از آدرس دهی یا اشاره گر ها ( Pointer)  انشاء الله در ارسال های بعدی تقدیم همه علاقه مندان خواهد شد.

 

 

سطح آموزشی: [3]   متوسط   (Intermediate)

دیدگاه‌ها

ازتون ممنوم میشم اگه برای افراد خیلی مبتدی مثل من یک ذره بیشتر توضیح بدهی!

 

 

ممنون

با عرض سلام و خسته نباشید

 

من روی یک الگوریتمی به زبان c کار میکنم که اجراش برام خیلی اهمیت داره اما متاسفانه وقتی مقدار متغیرها رو بالا میبرم خطای stack میده . آیا راهی هست که بشه طول stack رو افزایش داد یا به شیوه ای این مشکل رو برطرف کرد ؟؟؟

خطایی که ظاهر میشه با این مظمون هستش :

runtime check failure : stack around the varailabe x was corrupted

ممنون میشم راهنمایی بفرمایید

با سلام

در مورد خطای استک احتمالا شما آرایه ای رو بیشتر از حد مجازش نوشتید که این خطا رو میده.

مثلا آرایه ۳ درایه داره و شما نوشتید:

int x[3];

x[0] = 1;

x[1] = 2;

x[2] = 3;

x[3] = 4; /// error

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

هرچند لزومی به این کار نداره و شما اکه میزان مموری مورد نیازتون زیاده از Heap استفاده کنید. (توابعی مثل malloc یا calloc)

 

موفق باشید.

سلام خیلی ممنون عالی بود!

 

سلام دوست عزیز

ممنون میشم به سوالم پاسخ بدی.

برای پروژم یه الگوریتم برداشتم که ارور سرریزی پشته میده لطفا نحوه افزایش حافظه پشته رو برام توضیح بده خیلی وقت ندارم اگه میشه سریع تر برام ایمیل کن 

یک دنیا تشکر.

دمت گرم کمک کرد

افزودن نظر جدید

درباره من

سلام. به وب سایت شخصی من خوش آمدید.

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

 

Mohammad Zatkhahi Image

 

.

?>