توصیف کننده ها (مشخص کننده های سطح ذخیره سازی متغیرها)

Sarp

مدیر بازنشسته
یکی از دوستان تو خصوصی سوال پرسیده بود
من هم گرم نوشتن جواب واسه ایشون شدم و بعد زدن دکمه ارسال ، دیدم سیستم میگه پیام شما بیش از حد مجاز هست

گفتم کمی هم اضافه کنم به جواب ، و بذارمش تو تالار !

دوست عزیزمون در مورد توصیف کننده های نوع داده و مخصوصا auto توضیح خواسته بودند .

تو C++ چهار تا توصیف کننده نوع داده تعریف شده .
این توصیف کننده ها نحوه ذخیره شدن متغیرها رو تعیین میکنند . عبارتند از :

  • auto
  • static
  • extern
  • register
به توضیحی مختصر از هر کدوم میپردازم .

auto
این توصیف کننده برای معرفی متغیرهای اتوماتیک کاربرد داره .
منظور از متغیرهای اتوماتیک چیه ؟ متغیرهای اتوماتیک همون متغیرهای محلی هستند که به صورت پیشفرض دارای توصیف کننده auto هستند .
یعنی auto باشه و یا نباشه ، تاثیری در نحوه ذخیره سازی متغیر نخواهد داشت .
میشه گفت کلا کارائی نداره این توصیف کننده

این توصیف کننده تو زبان C هم هست و از همونجا به C++ هم به ارث رسیده .
جالبتر اینکه این توصیف کننده همچنین از B به C به ارث رسیده بود .
فکر نکنم برنامه ای به زبان C++ ببینید که این توصیف کننده توش مورد استفاده قرار گرفته باشه (به جز مثالها)
 
آخرین ویرایش توسط مدیر:

Sarp

مدیر بازنشسته
توصیف کننده static

این توصیف کننده رو میشه هم روی متغیرهای محلی (local) اعمال کرد و هم روی متغیرهای همگانی (global)
که با توجه به به اینکه روی کدام نوع متغیر اعمال میشه ، تاثیراتش متفاوت خواهد بود که هر دو رو توضیح میدم .

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

کد:
#include <iostream.h>
#include <conio.h>
void function()
{
    static int count = 0;
   count++;
   cout<<"count is "<<count<<'\n';
}
//***********************************
int main()
{
   for (int i=0 ; i<10 ; i++)
   function();
   getch();
   return 0;
}

فکر نکنم نیاز به تحلیل و تفسیر داشته باشه
میتونید کیورد static رو حذف کنید و نتیجه رو مقایسه کنید و به کاربرد این توصیف کننده پی برید .

توصیف کننده static برای متغیرهای همگانی یا گلوبال :
(کاش قبل این ، extern رو توضیح میدادم :D) ! اعمال توصیف کننده static روی یک متغیر همگانی باعث میشه آن متغیر فقط در فایل جاری قابل استفاده و دستیابی باشه .
یعنی اگر تابعی در این فایلی که متغیر همگانی استاتیک در آن قرار داره ، قرار نداشته باشه نمیتونه از این متغیر استفاده کنه .
همچنین در یک برنامه میشه از دو متغیر همنام تو دو فایل جداگانه بهره برد . البته این متغیر در یکی از فایلها باید با توصیف کننده static همراه باشه و در آن یکی فایل ، متغیری معمولی !

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

Sarp

مدیر بازنشسته
Extern

واسه پروژه های بزرگ نمیشه همه کدها رو تو یه فایل سرهم کرد .
C++ این امکان رو به برنامه نویس میده که برنامه رو تو فایلهای جداگانه بنویسه و هر فایل رو بعد کامپایل به همدیگه لینک کنه .واسه کسی که خلاقیت برنامه نویسی داره این سوال پیش میاد که یک متغیر همگانی که تو یه فایل تعریف شده چگونه توسط فایلهای دیگه قابل دستیابیه؟ !
یکی از پایه ای ترین تعاریف C++ اینه که توی یک برنامه ، یک متغیر همگانی رو فقط و فقط یک بار میشه تعریف کرد !
حالا شاید یه متغیری تو یکی از فایلها تعریف کرده ایم ، و تو یه فایل دیگه این متغیر نیاز هست که ازش استفاده کنیم !
چگونه باید به کامپایلر بفهمونیم که برنامه داره از یه متغیر همگانی که تو اون یکی فایل قرار داره استفاده میکنه ؟ (زبان آدمیزاد هم که سرش نمیشه)
جواب این مسئله در استفاده از توصیف کننده extern نهفته است .
به مثال (ناقص) زیر توجه کنید :

کد:
#include <iostream.h>
int count;
void function1();


main()
{
    int i;


   function1();
   cout<<i<<" ";


   return 0;
}

کد:
#include <iostream.h>
extern int count;

void function1()
{
    count = rand();
}

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

Sarp

مدیر بازنشسته
توصیف کننده register :

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

برای اینکه متوجه بشید استفاده از register تا چه حد در سرعت اجرای برنامه تاثیر میذاره ، برنامه زیر را کامپایل کنید :

کد:
#include <iostream.h>
#include <time.h>
int i;
main()
{
    register int j;
   int k;
   clock_t start , finish;

   start = clock();
   for(k = 0 ; k<100 ; k++)
       for(i = 0 ; i<3200 ; i++)
   finish = clock();
   cout<<"nonregister loop: "<<finish - start <<"ticks\n";

   start = clock();
   for(k=0 ; k<100 ; k++);
       for(j=0 ; j=3200 ; j++);
   finish = clock();
   cout<<"Register loop: "<<finish - start<<" ticks\n";

   return 0;
}

این برنامه از تابع clock() استفاده میکنه . کار این تابع اینه که تعداد تیکهای ساعت سیستم رو از زمان اجرای برنامه شمارش کرده و برمیگردونه .
الگوی این تابع :
کد:
[LEFT]Clock_t clock();
[/LEFT]
این تابع با هیدر فایل time.h تعریف میشه
هیدر فایل time.h نوع داده clock_t که تقریبا شبیه نوع داده long هست رو هم تعریف میکنه .
برای اینکه مدت زمان یک رخداد رو اندازه بگیریم باید clock() رو دقیقا قبل از آن رخداد فراخوانی کرد و مقداری که بازمیگردونه رو ذخیره کنیم . سپس بلافاصله پس از اتمام رخداد باید دوباره تابع clock() رو فراخوانی کرده و مقدار آغازین و پایانی رو از هم تفریق کنیم .
در برنامه هم ما برای اندازه گیری مدت زمان اجرای دو حلقه از این دو رویکرد استفاده کردیم . کنترل یکی از حلقه ها با یک متغیر register هست و یکی از حلقه ها با متغیر غیر رجیستر .

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

امروزه کامپایلرها تا حد ممکن برنامه رو بهینه کرده و سپس کامپایل میکنند .
در مثال بالا ما متغیر i رو گلوبال تعریف کردیم . چون کامپایلرها معمولا در صورت امکان متغیرهای محلی رو به نوع رجیستر تبدیل میکنند و برنامه رو بهینه میکنند .
پس ما i رو گلوبال تعریف کردیم تا به صورت اتوماتیک به نوع رجیستر تبدیل نشود .

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

Sarp

مدیر بازنشسته
در پست سوم عرض کردم یه نکته ای رو آخر میگم .

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

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

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

Sarp

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

به همین دلیل (اختصاص به نشریه) مطلب تا حد امکان خلاصه است .
ولی سعی کرده ام موضوع اصلی و مفهوم اصلی مطلب رو برسونم .
اگر هم کم و کسری داره به برنامه نویسی خودتون ببخشید :D

مطلب برداشتی ست آزاد از کتاب استاد کارگر و سایت cplusplus و همچنین اطلاعات خودم .
 
بالا