متغیرهای سراسری و محلی در زبان C
آموختیم که توابع چه هستند و همچنین در برنامه هایی که تاکنون نوشته شد، متغیرهای مورد نیاز هر تابع در داخل آن تابع تعریف شدهاند. یکی از مسائل مهم در مورد متغیرهای مورد استفاده در تابع، حوزه شناخت متغیر است که تعین می نماید متغیر در چه قسمتهایی از برنامه شناخته شده است و در چه قسمتهایی قابل استفاده نیست. در همه زبان های برنامه نویسی هنگامی که نامی از متغیرها به میان می آید، در مورد طول عمر آن ها هم بحث میگردد، منظور از طول عمر یک متغیر، زمانی است که از تعریف متغیر تا از بین رفتن آن سپری می شود. پس لازمه آن شناخت، متغیرهای سراسری و محلی در زبان C می باشد. که در این مبحث به صورت کلی به انواع متغیرهای سراسری و محلی و همچنین رده های ذخیره سازی متغیر ها در زبان برنامه نویسی C، آشنا خواهید شد.
۱- انواع متغیرها
تا کنون هر چه فرا گرفته اید فقط، یک تابع داشت که آن هم تابع اصلی بود ولی وقتی که چندین تابع داشته باشیم موضوع متفاوت می باشد و باید بدانیم که چطور باید از متغیر ها استفاده کنیم. بطور کلی متغیرها به دو دسته تقسیم می شوند:
۱-۱- متغیرهای محلی (local variable)
متغیرهای محلی، متغیرهایی هستند که در داخل یک بلاک {} تعریف شده اند و فقط در محدوده همان بلاک شناخته شده هستند. نمونه این دسته از متغیرها، متغیرهای محلی توابع هستند که در داخل بلاک مربوط به تابع تعریف می شوند و فقط در همان تابع شناخته شده هستند. البته دو تابع مختلف می توانند دارای متغیرهای همنام باشند، که در اینصورت این دو متغیر مجزا بوده و هیچ ارتباطی به یکدیگر ندارند. لازم به ذکر است که پارامترهای یک تابع نیز جزو متغیرهای محلی آن تابع محسوب می گردند.
نکته: گرچه معمولا متغیرهای محلی در داخل تابع تعریف می شوند، اما هر بلوک دلخواه می تواند دارای متغیرهای محلی باشد. مثلا یک دستور if مرکب می تواند در داخل بلوک خود، متغیرهای محلی را تعریف کند که فقط در داخل همان بلوک شناخته شده باشند.
۱-۲- متغیرهای سراسری (global variable)
متغیرهای سراسری، متغیرهای هستند که در خارج کلیه بلوکها و توابع از جمله main تعریف شده اند و در کل توابع برنامه (در حقیقت کل فایل مربوط به برنامه) شناخته شده و قابل استفاده می باشند. از آنجا که کلیه توابع برنامه به این متغیرها دسترسی دارند، هرگونه تغییری در این متغیرها توسط یکی از توابع، در سایر توابع نیز قابل رویت خواهد بود. به مثال های زیر توجه کنید:
متغیر سراسری// ; int a void main() { متغیر محلی برای int b; // mainمتغیر سراسری دراینجا قابل دسترسی است // ; a = 10 متغیر محلی دراینجا قابل دسترسی است // ; b = 5 if (a>0) { متغیر محلی برای بلوک // ; int c c = 8; متغیرهای aو bنیز در اینجا قابل دسترسی هستند // …. } متغیر cدر اینجا شناخته شده نیست // } void test() { متغیر محلی برای int c ; // test متغیر محلی در اینجا قابل دسترسی است // ;c = 5 متغیر سراسری در اینجا قابل دسترسی است // ;a = 20 }
نکته: درصورتیکه یک تابع دارای یک متغیر محلی همنام با یک متغیر سراسری باشد، در اینصورت هرگونه ارجاع به این نام مشترک، به متغیر محلی رجوع خواهد کرد.
چه موقع از متغیر های سراسری استفاده کنیم؟
متغیرهای سراسری هنگامی مفید هستند که یک داده بین چندین تابع بصورت مشترک استفاده شود. در این حالت نیازی به ارسال متغیرهای مشترک از طریق پارامترها نمی باشد. اما متاسفانه از آنجا که متغیرهای سراسری در کلیه توابع در دسترس هستند، ممکن است بصورت ناخواسته دچار تغییر شوند. علاوه براین اشکال زدایی آنها نیز بسیار مشکل است، چرا که محل بروز خطا مشخص نیست و هریک از توابع ممکن است مقدار متغیر را تغییر داده باشند. بنابراین در برنامه نویسی C، توصیه می گردد تا حد ممکن از متغیرهای سراسری استفاده نکنید.
۲- رده های ذخیره سازی
رده ذخیره سازی یک متغیر، مدت زمان حضور آن را در برنامه تعیین می نماید. بعبارت دیگر، رده ذخیره سازی تعیین می نماید یک متغیر چه موقع بوجود می آید و چه زمانی از بین می رود. در هنگام تعریف یک متغیر، باید به همراه نوع آن، رده ذخیره سازی آن را نیز مشخص کرد. چنانچه این کار صورت نپذیرد، کامپایلر از رده ذخیره سازی پیش فرض استفاده خواهد کرد. بطور کلی دو رده ذخیره سازی برای متغیرها وجود دارد:
۲-۱- رده ذخیره سازی اتوماتیک
متغیرهای متعلق به رده ذخیره سازی اتوماتیک، هنگام ورود به بلوکی که این متغیرها در آن اعلان شده اند، ایجاد شده و در طول اجرای این بلوک در حافظه وجود دارند؛ به محض خاتمه بلوک، این متغیرها نیز از بین رفته و حافظه آنها پس گرفته می شود. متغیرهای محلی(شامل پارامترهای توابع)، معمولا از این رده میباشند. بعنوان مثال، متغیرهای محلی یک تابع، به محض فراخوانی تابع ایجاد می شوند و در حین اجرای تابع در حافظه حضور دارند. با پایان یافتن اجرای تابع، این متغیرها نیز از بین می روند. این مسئله باعث می شود که صرفه جویی قابل توجهی در حافظه داشته باشیم. چرا که هرگاه به متغیری نیاز داریم ایجاد شده و با پایان یافتن کار نیز حافظه آن آزاد می شود. برای تعریف یک متغیر اتوماتیک، باید از کلمه کلیدی ،auto قبل از مشخصه نوع متغیر، استفاده نماییم.
void test(int a, int b) { auto int i; float k; … دستورات تابع // }
در تابع فوق، متغیر i از رده ذخیره سازی اتوماتیک تعریف شده است. اما نکته مهم اینجاست که متغیرهای محلی، بطور پیش فرض متعلق به رده ذخیره سازی اتوماتیک هستند. بدین معنا که چنانچه رده ذخیره سازی آنها بطور صریح مشخص نشود (مانند متغیر kدر مثال فوق)، از رده اتوماتیک در نظر گرفته می شوند. بنابراین در مثال فوق نه تنها متغیر i، بلکه متغیرهای محلی a ،k و b نیز از رده اتوماتیک در نظر گرفته شوند. به همین دلیل معمولا برنامه نویسان از کلمه کلیدی auto استفاده نمی کنند.
۲-۲- رده ذخیره سازی ایستا
متغیرهای متعلق به رده ذخیره سازی ایستا، از ابتدای آغاز برنامه ایجاد می شوند و تا پایان برنامه نیز در حافظه حضور دارند. متغیرهای سراسری به این دسته متعلق هستند. با شروع اجرای برنامه به متغیرهای سراسری حافظه تخصیص داده می شود. پس از آن کلیه توابع قادر به دیدن و تغییر مقدار آنها هستند، اما نمی توانند حافظه تخصیص یافته به این متغیرها را بازپس بگیرند. در پایان و پس از خاتمه تابع، main حافظه تخصیص یافته به این متغیرها باز پس گرفته می شود.
اما علاوه بر متغیرهای سراسری، متغیرهای محلی نیز می توانند از رده ذخیره سازی ایستا تعریف شوند. اگر یک متغیر محلی، بصورت ایستا تعریف گردد، فقط یکبار و آن هم در شروع اجرای برنامه ایجاد شده و مقدار اولیه خواهد گرفت (البته درصورتیکه به آن مقدار اولیه داده باشیم). پس از آن، با هر بار اجرای تابع، قادر به دسترسی به این متغیر خواهیم بود (چرا که یک متغیر محلی است)، اما با خاتمه تابع این متغیر از بین نرفته و مقدار آن تا فراخوانی بعدی تابع حفظ خواهد شد. متغیرهای محلی ایستا هنگامی از بین می روند که برنامه اصلی خاتمه یابد. از این متغیرها هنگامی استفاده می شود که بخواهیم مقدار یک متغیر محلی در فراخوانیهایمتوالی یک تابع حفظ شود (البته بدون اینکه سایر توابع به آن دسترسی داشته باشند، درغیر اینصورت آن را بصورت سراسری تعریف می کردیم).
برای تعریف یک متغیر محلی از رده ذخیره سازی ایستا، از کلمه کلیدی static استفاده می شود. البته استفاده از این کلمه الزامی است، چرا که متغیرهای محلی بطور پیش فرض از رده ذخیره سازی اتوماتیک درنظر گرفته می شوند.
* مثال زیر تفاوت متغیرهای محلی اتوماتیک و ایستا را نشان می دهد.
#include <stdio.h> void computeSum(int number) { int autoSum = 0; static int staticSum = 0; autoSum += number; staticSum += number ; printf(“autoSum = %d and staticSum = %d \n”,autoSum,staticSum); } void main() { int i; for (i=1; i<=5; i++) computeSum(i) ; }
خروجی کد بالا:
autoSum = 1 and staticSum = 1 autoSum = 2 and staticSum = 3 autoSum = 3 and staticSum = 6 autoSum = 4 and staticSum = 10 autoSum = 5 and staticSum = 15
روش بکارگیری یک متغیر سراسری در توابع با چند فایل مختلف
نوع دیگری از رده ذخیره سازی ایستا، رده extern است. این رده هنگامی بکار می رود که برنامه در چندین فایل مختلف قرار داشته باشد. در برنامه های بزرگ که دارای توابع متعددی هستند، برای جلوگیری از بزرگ و پیچیده شدن بیش از حد فایل برنامه، آن را بطور منطقی به چندین فایل تقسیم می کنند. بدین صورت که کلیه توابع و داده های مرتبط با یکدیگر را در یک فایل قرار می دهند. سپس هر فایل بصورت مجزا کامپایل شده و درنهایت تمامی آنها با یکدیگر پیوند خورده و تشکیل فایل اجرایی نهایی را می دهند. این روش باعث مدیریت بهتر پروژه های بزرگ می شود.
اما مشکل هنگامی است که بخواهیم یک متغیر سراسری را در توابع موجود در چند فایل مختلف، مورد استفاده قرار دهیم. مسلما این متغیر سراسری باید در یکی از فایلها تعریف شود. اما در مورد سایر فایلها چه بایدبکنیم؟ متاسفانه هریک از دو راه زیر منجر به شکست می شود:
- اگر متغیر را در سایر فایلها بدون تعریف مجدد استفاده نماییم، کامپایلر اعلام خطا کرده و متغیر را نخواهد شناخت.
- اگر متغیر را در سایر فایلها نیز تعریف مجدد نماییم، کامپایلر اعلام خطا نخواهد کرد، اما پیوند زننده در هنگام ترکیب فایلها با هم متوجه تعریف چند متغیر با نام یکسان شده و اعلام خطا خواهد کرد.
تنها راه حل آن است که این متغیر را در یک فایل تعریف کرده و سپس در سایر فایلها آن را بعنوان یک متغیر خارجی (extern) اعلان (و نه تعریف) نماییم. مشخصه externبه کامپایلر اعلان می کند که این متغیر در جای دیگری (معمولا یک فایل دیگر) تعریف شده است و بنابراین می تواند بدون اعلان خطا از آن استفاده کند، اما این مشخصه باعث تعریف مجدد متغیر نمی گردد. برای تعریف متغیر به شکل خارجی بصورت زیر عمل می کنیم:
extern int a;
به مثال زیر توجه کنید:
توضیح برنامه: برنامه دارای دوفایل به نامهای File1.C و File2.C می باشد. توجه کنید که فقط یکی از این دو می تواند دارای تابع main باشد. متغیر سراسری k در فایل File1.C تعریف شده است بنابراین توسط توابع آن از جمله F1 قابل دسترسی است. اما این متغیر در فایل File2.C نیز بصورت خارجی تعریف شده است و بنابراین در توابع این فایل مانند تابع F2 نیز قابل دسترسی است. توجه کنید که متغیر k که توسط تابع F2 مورد دسترسی قرار گرفته است، همان متغیر سراسری تعریف شده در فایل File1.C است و در حقیقت هر دوفایل از یک متغیر k مشترک استفاده می کنند.
* در مثال زیر انواع متغیرها و نحوه کار آنها بررسی شده است.
#include <stdio.h> int x = 1; // global variable x void a() { int x = 10 ; // local automatic variable x printf("x in function a is %d \n",x); x ++; printf("x in function a is %d \n",x); } void b() { static int x = 20 ; // local static variable x int k = 0; void F1() { …. دسترسی به متغیر سراسری k ++; // k … } void main() { …. } extern int k; void F2() { … دسترسی به متغیر سراسری مشترک // ; -- k … } void F3() { … } File1.C File2.Cprintf("x in function b is %d \n",x); x ++; printf("x in function b is %d \n",x); } void c() { printf("x in function c is %d \n",x); x ++; printf("x in function c is %d \n",x); } void main() { printf("x in function main is %d \n",x); a() ; b() ; c() ; printf("x in function main is %d \n",x); a() ; b() ; c() ; printf("x in function main is %d \n",x); }
خروجی کد بالا:
x in function main is 1 x in function a is 10 x in function a is 11 x in function b is 20 x in function b is 21 x in function c is 1 x in function c is 2 x in function main is 2 x in function a is 10 x in function a is 11 x in function b is 21 x in function b is 22 x in function c is 2 x in function c is 3 x in function main is 3
توضیح برنامه: در شروع برنامه، از آنجا که در تابع main متغیر xوجود ندارد، در نتیجه مقدار متغیر سراسری یعنی ۱چاپ شده است.اما در هنگام فراخوانی تابع ،a از آنجا که متغیر x بصورت محلی تعریف شده است، هرگونه استفاده از این متغیر به نمونه محلی کن مراجعه می کند و از نمونه سراسری استفاده نمی شود. بنابراین مقدار متغیر محلی یعنی ۱۰چاپ شده و سپس مقدار آن یک واحد افزایش یافته است. همین مسئله در مورد تابع b نیز برقرار بوده و در نتیجه مقدار متغیر محلی یعنی ۲۰ چاپ شده و سپس یک واحد افزایش یافته است. اما بدلیل تعریف متغیر x بصورت ایستا، پس از پایان تابع، مقدار این متغیر حفظ خواهد شد. در فراخوانی تابع ، c از آنجا که متغیر محلی تعریف نشده است، در نتیجه از همان متغیر سراسری، x استفاده شده و مقدار کن یعنی ۱چاپ می شود و سپس یک واحد به آن اضافه شده است. پس از بازگشت به، main مجددا مقدار متغیر سراسری x چاپ شده است که برابر ۲ است. این مسئله نشان می دهدکه تغییر اعمال شده در تابع c برروی متغیر سراسری، x به تابع main نیز منتقل شده است.
فراخوانیهای بعدی این توابع نیز مشابه حالت قبلی است، تنها نکته جالب آن است که از آنجا که متغیر x در تابع a بصورت اتوماتیک تعریف شده، در فراخوانی دوم مجددا با ۱۰مقداردهی اولیه شده است و مقدار قبلی از بین رفته است. اما در تابع b که متغیر x بصورت ایستا تعریف شده است، در فراخوانی دوم مقدار قبلی یعنی ۲۱ حفظ شده است.