زبان برنامه نویسی سی/کلاس‌های ذخیره

ویکی‎کتاب، کتابخانهٔ آزاد
پرش به ناوبری پرش به جستجو
Gnome-go-last.svg
Gnome-go-first.svg

تعیین کننده‌های کلاس ذخیره Class-Storage Specifiers

در این مبحث می‌پردازیم به کاربرد پنج کلیدواژه زبان C، یعنی کلیدواژه‌های auto و static و extern و register و همچنین کلیدواژه volatile که جزء کلاس‌های ذخیره حساب نمی‌شود اما جایی بهتر برای مطرح نمودن آن وجود ندارد. پیش‌تر، کمی با کلیدواژه volatile که به معنی داده فرّار است و باعث می‌شود تا مقداردهی یا دسترسی به مقدار و موجودی داده به سخت‌افزار واگذار شود آشنا شدید که مقداری به کلاس‌های ذخیره مشابه است. پیش از آغاز معرفی کلیدواژه‌ها لازم است با مفاهیم حوزه دید (view scope) که به صورت مختصر حوزه (scope) نیز خوانده می‌شود و طول عمر (lifetime) آشنا شوید

حوزه و طول عمر[ویرایش]

در برنامه‌نویسی حوزه دید view scope یا visibility به حوزه و محدوده‌ای گفته می‌شود که یک داده در متن برنامه قابل رؤیت توسط بقیه داده‌ها و کدهای نوشته شده می‌باشد و به آن دسترسی دارند تا مقدار و موجودی داخل آن را بخوانند یا تغییر دهند؛ بنابراین در بسیاری از زبان‌های برنامه‌نویسی از جمله زبان سی، برخی از داده‌ها از دید داده‌ها و کدهای دیگر پنهان هستند. کاربرد حوزه در برنامه‌نویسی، جدا کردن بخش‌هایی از برنامه است که جدای از یکدیگر عمل کنند که مهم‌ترین مزیّت آن این است که تغییر در بحشی از برنامه، قسمت‌های دیگر را تحت تأثیر قرار نمی‌دهد و قسمت‌های دیگر، مجزا و بدون تحت تأثیر قرار گرفتن نسبت به آن کد به کار خود ادامه می‌دهند. در زبان C هر متغیّری که داخل بلوکی از هر تابع یا هر دستور (مثل حلقه while) قرار بگیرد، یک متغیّر محلّی (local variable) محسوب می‌شود. بدین معنا که آن متغیر، خارج از محدوده بلوکی که در آن تعریف شده است، غیرقابل دسترسی می‌باشد مگر به واسطه و با کمک یک متغیر سراسری که در صورت اشاره‌گر بودن متغیر (اشاره‌گر سراسری) می‌توانیم مقدار به روز متغیر محلی را به کار ببریم یا مقدار متغیر محلی را خارج از بلوک تغییر دهیم. در بین کلیدواژه‌هایی که از آن‌ها نام بردیم کلیدواژه auto به ندرت به کار می‌رود. این کلیدواژه باعث محلی شدن یک متغیر می‌شود؛ یعنی متغیر را خودکار یا automatic می‌کند تا در داخل بلوک قابل دسترسی و خارج از آن غیرقابل دسترس باشد (آن را محلی می‌کند) و همچنین علاوه بر این باعث از بین رفتن مقدار و موجودی ذخیره شده در داخل متغیر و آزاد شدن فضای اشغال شده توسط آن متغیر در حافظه؛ پس از به پایان رسیدن بلوک می‌شود. به این مسئله طول عمر یک متغیر می‌گوئیم: یعنی مدت زمانی که در حین اجرای برنامه نوشته شده؛ متغیر در داخل حافظه موقّت کامپیوتر باقی می‌ماند. تمام متغیرهای محلی پس از به پایان رسیدن بلوکِ خود از بین می‌روند؛ بنابراین حتی بدون قید کردن کلیدواژه auto در داخل بلوک برای متغیرها؛ متغیرهای ما خود به خود محلی هستند. اما در کنار کلیدواژه auto کلیدواژه‌های static که دو کاربرده است (و دو مفهوم مجزا دارد که یکی مربوط به دائمی کردن طول عمر متغیرهای محلی و کاربرد دیگر آن در اختصاصی کردن متغیر در فایل‌های برنامه نوشته شده می‌باشد) و کلیدواژه extern که اجازه دسترسی به داده را خارج از متن برنامه نوشته شده فراهم می‌کند و یا مقدار دهی به آن را در قسمتی دیگر از فایل‌های برنامه، امکان‌پذیر می‌نماید و همچنین کلیدواژه register که باعث ذخیره شدن متغیر در cache سی‌پی‌یو به جای حافظه موقت در کامپیوتر می‌شود؛ در این مبحث معرفی می‌شوند. در کنار کلیدواژه volatile که با آن آشنایی دارید و در قسمت ابتدایی مبحث نیز بدان اشاره نمودیم

در زبان C هر جفت آکولاد (باز و بسته) از هر تابع یا دستوراتی شامل for و while و do while و switch و if و else if و else یک بلوک نامیده می‌شود. هر متغیری که خارج از هر بلوکی (و عموماً در ابتدای برنامه نوشته شده، پس از پیش‌پردازنده‌ها) اعلان شود؛ حوزه سراسری دارد؛ بدین معنا که پس از اعلان در سرتاسر برنامه نوشته شده قابل دسترسی و استفاده است. همچنین مقداری که به داده سراسری در هر بلوک داده می‌شود متعلق به همان بلوک است. یعنی مقداری که یک متغیر سراسری در بلوک تابع func1 دارد می‌تواند پس از رسیدن به تابع func2 تغییر کند و در func1 یک مقدار دارد و در func2 مقدار دیگری و آخرین مقدار متغیر سراسری؛ آخرین مقداری است که در برنامه به آن داده شده است. متغیرهای سراسری دارای طول عمر دائمی می‌باشند و در تمام طول اجرای برنامه، متغیر وجود خواهد داشت و مقدار آن محفوظ است. با نسبت دادن کلیدواژه extern متغیر ما می‌تواند حتی خارج از فایل فعلی نوشته شده برنامه نیز قابل دسترسی باشد که در قسمت مربوطه (extern) در همین مبحث به آن می‌پردازیم

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

همچنین متغیرهایی که به عنوان پارامتر تابع یا متغیر شرطی و یا کنترلی دستورهای مذکور؛ اعلان شوند؛ فقط داخل بلوک همان تابع یا دستور، قابل دسترسی هستند و خارج از آن تابع یا دستور، قابل رؤیت توسط مابقی کدهای برنامه نیستند. در مورد این متغیرها نیز؛ تنها دستورهای داخلی‌تر به متغیرهای دستورهای خارجی‌تر دسترسی دارند. همچنین متغیری که داخل تابع پیش از دستورات داخل بلوک (بیرون از بلوک‌های دستورها) اعلان شود توسط تمام بلوک‌های دستورهای بعدی قابل دسترسی می‌باشد

با چند مثال و تشریح آن‌ها مطالب بالا را به صورت عملی بیان می‌کنیم

# include <stdio.h>

void func1(void);
void func2(void);
int i = 7;

int main()
{

    func1();
    func2();

    return 0;
}

void func1(void)
{
    i = 24;
    printf("%d\n", i);
}

void func2(void)
{
    i = 16;
    printf("%d\n", i);
}

در مثال بالا ابتدا با دستور مستقیم و پیش‌پردازنده include فایل سرآیند stdio (که مخفف standard input/output است) را ضمیمه فایل برنامه نمودیم (تا بتوانیم از تابع کتابخانه‌ای printf استفاده کنیم) دو تابعی را که قصد استفاده از آن‌ها را داشتیم، اعلان نمودیم (یعنی func1 و func2). یک متغیر سراسری با نام i ایجاد نمودیم و به آن مقدار ۷ را اختصاص دادیم؛ که همان طور که ملاحظه می‌کنید، متغیر خارج از بلوک تابع‌ها یا دستورها قرار دارد (خارج از آکولادهای باز و بسته) سپس تابع main که بخشی از زبان C است (هنوز به این مبحث نرسیده‌ایم) و تابع اصلی برنامه می‌باشد که تابع‌های دیگر را در آن احضار می‌کنیم و برنامه پس از کامپایل از طریق همین تابع به اجرا در خواهد آمد. داخل تابع main دو تابع اعلان شده را احضار نموده‌ایم (یعنی func1 و func2) که بعد از تابع main تعریف شده‌اند؛ در پایان تابع main نیز دستور return را با مقدار ۰ استفاده نموده‌ایم تا مشخص شود که تابع مقداری را قرار نیست بازگرداند. تابع func1 مقدار متغیر سراسری i را که به آن دسترسی دارد تغییر داده و به آن مقدار ۲۴ را تخصیص داده است و مقدار آن را توسط تابع کتابخانه‌ای printf (مخفف print formatted که برای نمایش متن در محیط‌های خط دستوری به کار می‌رود) به خروجی خط دستوری ارسال می‌کند و در نهایت نمایش داده خواهد شد. سپس تابع func2 را همانند func1 تعریف نمودیم با این تفاوت که مقدار ۱۶ را به متغیر سراسری i تخصیص دادیم که مقدار متغیر به روز خواهد شد. آخرین مقدار باقی مانده در i عدد ۱۶ خواهد بود و پس از اتمام اجرای برنامه، مقدار آن و خودش از بین خواهد رفت. شما می‌توانید متن همین برنامه را کپی کرده و داخل یک IDE مثل Visual Studio از مایکروسافت یا Code::Blocks بچسانید (paste کنید) متن را داخل یک سند از قالب c ذخیره نمائید (مثل test.c) و سپس از طریق کلیک بر روی منوی Build گزینه Compile and Build را کلیک کنید و سپس Run را تا نتیجه را ببینید. اگر IDE ندارید؛ کامپایلر ریز C را دانلود کرده و سپس از طریق رابط خط دستوری در سیستم عامل خود (cmd در ویندوز و Terminal در لینوکس و مکینتاش، به طور مثال) دستور زیر را وارد کنید:

در ویندوز:

compiler-directory/tcc.exe -o test.exe address-of/test.c

که address-of و compiler-directory به ترتیب آدرس فایل برنامه نوشته شده شما و آدرس فولدر کامپایلر ریز سی (یعنی tcc) می‌باشد

در لینوکس:

sudo tcc -o test address-of/test.c

سپس آدرس test (که فایل قابل اجرا می‌باشد) را در محیط خط دستوری وارد کنید تا اجرا شود و نتیجه را ببنید

مثال دوم:

# include <stdio.h>

void func1(void)
{
    int i = 8;
    printf("%d\n", i);
}

int main()
{

    printf("%d\n", i);
    func1();

    return 0;
}

در این مثال تابع func1 را همزمان، اعلان و تعریف نموده‌ایم. داخل بلوک func1 یک متغیر با نام i اعلان و تعریف نموده‌ایم؛ بنابراین محلی است و خارج از دسترس بقیه کدها که بیرون از بلوک func1 هستند. در داخل تابع main تلاش کرده‌ایم تا مقدار i را همانند تابع func1 نمایش دهیم. اما اگر کد بالا را بخواهد کامپایل کنید، دیباگر (debugger، اشکال زدا) از شما ایراد خواهد گرفت که i چیزی تعریف نشده و ناشناخته است myt.c:12: error: 'i' undeclared

چرا که دسترسی به آن امکان‌پذیر نیست. حال اگر خط printf("%d\n", i); را توسط دو ممیز (دو اسلش / به صورت //) به کامنت تبدیل کنیم و سپس دوباره بخواهیم آن را کامپایل کنیم، بدون ایراد کامپایل شده و سپس می‌توانیم آن را اجرا کنیم تا خروجی ۸ را در محیط خط دستوری مشاهده کنیم

مثال سوم:

# include <stdio.h>

void func1(void);
void func2(void);
int j = 14;

int main()
{
    func1();
    func2();

    return 0;
}

void func1(void)
{
    int m = 63;
    j = m;
    printf("%d\n", m);
}

void func2(void)
{
    printf("%d\n", j);
}

در این مثال دو تابع داریم و یک متغیر سراسری j از نوع صحیح (int) که مقدار آن را ۱۴ تعریف کرده‌ایم. داخل تابع func1 یک متغیر محلی داریم با نام m که مقدار ۶۳ را در خود جای می‌دهد؛ سپس داخل همان تابع مقدار m را داخل متغیر سراسری j می‌گذاریم. حالا j مقدار ۶۳ دارد، تابع func1 با نمایش مقدار m به پایان می‌رسد. تابع func2 مقدار j را نمایش می‌دهد که همان مقدار m یعنی ۶۳ می‌باشد و شما می‌توانید با کامپایل برنامه؛ خروجی را ببینید. دقت کنید که یک متغیر ساده نمی‌تواند مقدار فعلی تابع را در صورتی که متغیر محلی خود را static (ایستا) کنیم در خود جای دهد؛ باید یک اشاره‌گر سراسری تعریف نموده و داخل تابع به متغیر مورد نظر خود اشاره دهیم و سپس بیرون از بلوک تابع به متغیر محلی مورد نظر دسترسی خواهیم داشت تا مقدار حال حاضر آن متغیر را بخوانیم و یا مقدار آن را خارج از بلوک تغییر دهیم

مثال چهارم:

# include <stdio.h>

void func1(int i);
void func2(void);
int num = 5;

int main()
{
    func1(num);
    func2();

    return 0;
}

void func1(int i)
{
    for(i=0; i<num; i++)
    {
    printf("%d\n", i);
    }
}

void func2(void)
{
    printf("%d\n", i);
}

در مثال چهارم تابع func1 یک پارامتر دارد با نام i که بر روی آن پردازش انجام می‌دهد. برخلاف func1 تابع func2 هیچ پارامتری ندارد و بنابراین آرگومانی نمی‌پذیرد. یک متغیر سراسری با نام num داریم که مقدار ۵ را به آن اختصاص داده‌ایم. در داخل تابع اصلی برنامه یعنی main دو تابع خود را که در ادامهٔ main تعریف نموده‌ایم؛ اخضار کرده‌ایم که مقدار متغیر num را به عنوان آرگومان به func1 نسبت داده‌ایم تا بر روی آن پردازش انجام دهد. تابع func1 پارامتر i را دارد که توسط دستور حلقه‌ای for (هنوز به مبحث آن نرسیده‌ایم و اجمالاً آن را توضیح می‌دهیم) از i به مقدار ۰ تا زمانی که به مقدار num برسد، i را یکی یکی اضافه می‌کند و مقدار آن در خروجی خط دستوری نمایش می‌دهد. اما تابع func2 در تلاش است تا در بدنه خود و داخل بلوک خود به متغیر i که پارامتر تابع func1 است دسترسی پیدا کند که مسلماً مجاز نیست و در صورت تلاش برای کامپایل برنامه، دیباگر به ما خواهد گفت که متغیر i در خط بیست و پنجم ناشناخته و اعلان‌نشده است؛ ولی با حذف اعلان، احضار و تعریف تابع func2 در کد بالا و کامپایل آن، در خط دستوری در صورت اجرای برنامه نوشته شده فوق خروجی:


0
1
2
3
4

را مشاهد خواهید نمود

مثال پنجم:

# include <stdio.h>

void func1(void);

int main()
{
    func1();

    return 0;
}

void func1(void)
{
    int k=8;
    for(int i=0; i<5; i++)
    {
        for(int m = 0; m<k; m++)
        {
        printf("%d\n", i);
        }
    printf("%d\n", m);
    }
}

در مثال بالا، تابع func1 را می‌بینید که در تعریف خود داخل بلوک خود یک متغیر قابل دسترسی برای تمام بلوک‌های داخل خود با نام k دارد. حلقه for اولی که بیرونی‌تر است یک متغیر را اعلان و تعریف نموده است تا از مقدار ۰ تا قبل از ۵ که می‌شود ۴ حلقه for داخلی‌تر خود را تکرار کند (که می‌شود ۵ بار) و در هر بار تکرار مقدار متغیر m را که داخل حلقه for داخلی‌تر تعریف شده است در خروجی خط دستوری نمایش دهد؛ اما همین مسئله باعث می‌شود تا کامپایلر از ما خطا بگیرد که m اعلان‌نشده است. پس خط ۲۱ را که شامل کد printf("%d\n", m); می‌شود حذف نمائید و سپس کد را کامپایل کنید. خواهد دید که حلقه for داخلی‌تر ۸ بار اجرا می‌شود (که ۵ بار تکرار شده است) و از m مساوی ۰ شروع می‌کند و تا m مساوی ۷ ادامه می‌دهد و در هر بار i را نمایش می‌دهد منتها خود for داخلی‌تر ۵ بار اجرا می‌شود و در هر بار مقدار i از ۰ تا ۴ افزایش می‌یابد پس عددهای ۰ تا ۴ هر کدام ۸ بار نمایش داده می‌شوند. هنوز به مباحث دستورها نرسیده‌ایم. این مثال تنها برای یاد گیری حوزه دید در زبان C می‌باشد. همان طور که در مثال بالا مشاهده نمودید متغیر k در داخل بلوک‌ها قابل دسترسی بود ولی حلقه for بیرونی‌تر به متغیر حلقه for داخل خود دسترسی نداشت (یعنی متغیر m) اما حلقه for داخلی به متغیر i که داخل حلقه بیرونی تعریف شده است دسترسی داشت

دقت داشته باشید که متغیرهای سراسری پس از اعلان توسط کامپایلر مقدار ۰ می‌گیرند مگر اینکه شما در ادامه به آنها مقدار معینی بدهید که در واقع آنها را تعریف می‌کنید؛ همچنین متغیرهای کاراکتری سراسری مقدار پوچ گرفته ('۰\') و اشاره‌گرهای سراسری مقدار تهی می‌گیرند (NULL) اما متغیرهای محلی در صورتی که به آنها مقداری تخصیص ندهید و فقط اعلان کنید مقدار آشغال و زباله می‌گیرند (garbage value). مقادیر زباله، اصطلاحاً مقادیری هستند که در حافظه موقت کامپیوتر، متعلق به برنامه‌های دیگری هستند؛ بنابراین زمانی که متغیر محلی را اعلان می‌کنید ولی آن را تعریف نمی‌کنید، متغیر اشاره می‌کند به یک خانه تصادفی در حافظه موقت که ممکن هست هر مقداری داخل آن موجود باشد؛ بنابراین فراموش نکنید که متغیرها را باید حتماً تعریف کنید، حتی استفاده از متغیرهای سراسری تعریف نشده نیز توصیه نمی‌شود و بهتر است به آنها هم مقداری را تخصیص بدهید و سپس پردازش خود را اعمال کنید

دقت کنید

شما در اعلان و تعریف هر متغیر، باید به مفهوم تعیین‌کننده کلاس ذخیره‌ای که می‌خواهید استفاده کنید، توجه کنید. مثلاً وقتی یک متغیر را static می‌کنید دیگر نمی‌توانید آن را extern کنید. اما می‌توانید یک متغیر ایستا را (static) رجیستر کنید (register)، متغیرهای خارجی را (extern) نیز می‌توانید تعریف کنید تا در رجیسترهای کش CPU ذخیره شوند. در هر مبحث این مسئله را بیشتر در مورد هر تعیین‌کننده کلاس ذخیره توضیح می‌دهیم

متغیرهای خود کار auto[ویرایش]

کلیدواژه auto که مخفف automatic می‌باشد باعث خودکار شدن متغیر می‌شود تا متغیر ما محلی باشد؛ بدین معنا که داخل بلوک قابل دسترسی و خارج از آن غیرقابل دسترس باشد و همچنین پس از به پایان رسیدن عمل تابع، متغیر را از بین می‌برد. متغیرهای محلّی به صورت پیش‌فرض متغیرهایی خودکار (automatic variables) هستند اما اگر متغیر سراسری‌ای در متن برنامه خود داشته باشید که بخواهید آن را داخل یک یا چند بلوک به صورت محلی در بیاورید می‌توانید داخل بلوک یا بلوک‌ها همان متغیر سراسری را به صورت خودکار اعلان و تعریف نمائید (با نوشتن کلیدواژه auto پیش از نوع داده و شناسه متغیر مورد نظر) این امر چندان به کار نمی‌آید و ریسک پذیر است و ممکن است شما را به خطا بیاندازد؛ اما گاهی برای اینکه یک داده چند صورت داشته باشد و در هر صورت خود یک کار منحصر به فرد خود را انجام دهد از این ترفند استفاده می‌کنیم. مشابه همین مسئله در مورد پیوندسازی داخلی (internal linkage) توسط کلیدواژه static در مورد متغیرهای سراسری، صادق است. یعنی چنین متغیری با همان نام (شناسه) و با همان نوع داده یا نوع دیگری از داده را داخل یک متن کد دیگر اعلان و تعریف می‌کنید تا کاربرد دیگری داشته باشد (مثلاً تابع func1 در داخل متن برنامه فعلی با یک روش مرتب‌سازی می‌کند و تابع دیگری در متن برنامه دیگری از پروژه با همان نام یعنی func1 تعریف می‌کنید) که البته به مبتدی‌ها توصیه نمی‌شود و به راحتی ممکن است باعث اشتباه گرفته شدن دو یا چند داده (که می‌تواند تابع نیز باشد) با همدیگر می‌شود. برای دسترسی جداگانه به چنین داده‌هایی که نام یکسانی دارند باید به تعداد متناسب با آنها اشاره‌گر تعریف کنید که به آنها اشاره داده شده‌اند و در هر جای برنامه که به هر کدام احتیاج داشتید از اشاره‌گر اشاره کننده به آن داده‌ها استفاده کنید

مثال:

# include <stdio.h>

void func1(void);
void func2(void);
int m = 15;

int main()
{
    func1();
    func2();

    return 0;
}

void func1(void)
{
    auto int m = 17;
    printf("%d\n", m);
}

void func2(void)
{
    printf("%d\n", m);
}

در مثال بالا متغیر m سراسری است و به آن مقدار ۱۵ اختصاص داده شده است. تابع func1 متغیری با همان نام در داخل بلوک خود تعریف نموده است که می‌دانید محلی است و پس از احضار تابع func1 در تابع main مقدار ۱۷ را در خروجی نمایش خواهد داد. اما با احضار تابع func2 مقداری که نمایش داده می‌شود، همان مقدار ۱۵ است که در داخل متغیر سراسری m وجود دارد؛ بنابراین مقدار m در سرتاسر برنامه به غیر از داخل تابع func1 (که داخل آن متغیری با همان نام اما محلی ایجاد کرده‌ایم) مقدار متغیر m همان ۱۵ خواهد بود اما متغیری که داخل تابع func1 محلی شده است و مقدار جداگانه ۱۷ را در خود جای داده است. با پایان یافتن تابع func1 که داخل تابع main احضار شده است، متغیر محلی ما از بین می‌رود اما متغیر سراسری تا آخرین لحظه‌ای که برنامه در حال اجرا است داخل حافظه موقت باقی می‌ماند

متغیرهای ایستا static[ویرایش]

کلیدواژه static که به معنی ایستا می‌باشد دو کاربرد و مفهوم در زبان C دارد:

۱ - ایستا کردن متغیرهای محلی؛ بدین معنی که مقدار متغیر مورد نظر پس از پایان یافتن بلوک تابع از بین نمی‌رود و در داخل حافظه موقت تا انتهای زمان اجرای برنامه باقی می‌ماند؛ بنابراین با نوشتن کلیدواژه static پیش از نوع داده متغیر خود، به کامپایلر تفهیم می‌کنید که در فایل اجرایی، برنامه‌ای را بنویسد که در زمان اجرای برنامه، متغیر شما را ایستا تعریف می‌کند تا مقدار آن از بین نرود. متغیرهای ایستا، همانند متغیرهای سراسری، در زمان اعلان مقدار ۰ می‌گیرند که در صورت مقداردهی توسط شما، مقدار داده شده را خواهند گرفت (که همانند متغیرهای سراسری در مورد کاراکترها، پوچ و در مورد اشاره‌گرها تهی - NULL - می‌باشد)

در زبان‌هایی مثل C متغیرهای ایستا طبق الگوی سیستم عامل در Data Segment یعنی سگمنت به خصوصی از حافظه که به آن Data Segment می‌گویند، ذخیره می‌شود (برای کسب اطلاعات بیشتر در مورد الگوی اختصاص حافظه موقت توسط سیستم عامل که مبتنی بر الگوهای سخت‌افزاری نیز می‌باشد؛ به صفحه رو به رو مراجعه کنید: https://en.wikipedia.org/wiki/Data_segment) در نوشتن سیستم عامل نیز باید الگوی مشخصی برای تخصیص حافظه موقت توسط برنامه‌ها داشته باشید که این الگو از دیرباز تا کنون در چارچوب خاصی بوده است که در صفحه‌ای که به آن ارجاع داده‌ایم و پیوندهای آن مفصلاً توضیح داده شده است

در زبان C، طبق استاندارد به متغیرهای ایستا، تنها می‌توانید مقادیر صریحی بدهید و نمی‌توانید مقدار آنها را یک تابع قرار دهید تا مقدار خروجی آن در متغیر ایستا ذخیره شود

مثال:

# include <stdio.h>

void func1(void);
void func2(void);
int count = 5;

int main()
{
    func2();

    return 0;
}

void func1(void)
{
    static int i = 1;
    printf("%d time(s) func1 loaded\n", i);
    i++;
}
void func2(void)
{
    while(count>0)
    {
    func1();
    count--;
    }
}

یکی از کاربردهای متغیرهای ایستا این است که بررسی کنیم، تابع چندبار به اجرا در آمده است. در مثال بالا برنامه در صورت کامپایل و اجرا در هر دقعه نشان می‌دهد که چندمین بار است که تابع func1 به اجرا در آمده است. از آنجایی که هنوز به برخی از مباحث نرسیده‌ایم به صورت اختصار می‌گوئیم که تابع func1 متغیر ایستا i را تعریف نموده و آن را به خروجی ارسال می‌کند و سپس یک واحد به مقدار i اضافه می‌کند؛ تابع func2 پنج دفعه تابع func1 را با حلقه while به اجرا در می‌آورد. اما دقت کنید که اگر متغیر i ایستا نبود در هر دفعه اجرا با پیام:

1 time(s) func1 loaded

مواجه می‌شدید (چرا که مقدار آن در هر بار پایان یافتن تابع از بین می‌رفت و با احضار مجدد تابع، مقدار ۱ می‌گرفت؛ متغیر محلی، غیرایستا می‌شد)

شما نمی‌توانید هم‌زمان یک متغیر محلی را auto و static اعلان یا تعریف کنید

۲ - اختصاصی کردن متغیر سراسری در متن فعلی برنامه. همان طور که پیش‌تر گفتیم، در نوشتن برنامه‌ها برای بهتر خوانده شدن آن یا تیمی انجام دادن پروژه؛ شما ناچارید تا برنامه را در چندین، چند صد و یا حتی چند هزار فایل متن برنامه مجزا بنویسید. اگر در یکی از این فایل‌های متنی برنامه خود یک متغیر سراسری را (که می‌تواند یک تابع باشد) به صورت static تعریف کنید از حوزه دید بقیه فایل‌ها، پنهان می‌ماند و اگر از آن متغیر بخواهید استفاده کنید کامپایلر از شما ایراد خواهد گرفت که متغیر مورد نظر اعلان نشده است و شناسه مورد استفاده ناشناس است. کاربرد این نوع متغیرها را در مبحث auto کمی بیان نمودیم. گاهی می‌خواهید تا از یک نام برای یک تابع یا متغیر، چند استفاده بکنید و شناسه شما بیانگر چند نوع عملکرد باشد. بدین ترتیب می‌توانید یک نام را برای چند متغیر و یا تابع مجزا در نظر بگیرید و آنها را اعلان و تعریف کنید. البته دقت کنید که این مسئله توسط استاندارد تعریف شده است و بعضاً هم به کار می‌رود اما استفاده از آن توصیه نمی‌شود و ممکن است باعث خطای شما در برنامه و نهایتاً برنامه خروجی می‌شود

برای استفاده از چنین متغیرهای مجزایی در سراسر برنامه خود باید از اشاره‌گرها استفاده کنید تا مثلاً در متن فعلی از متغیر سراسری static خود استفاده کنید و در ادامه از متغیر سراسری static دیگری با همان نام که در فایل متن دیگری تعریف شده است استفاده کنید و یا متغیر غیر تابع شما مقدار خود را به خروجی یک تابع بازگرداند تا تابع حاوی آن متغیر را احضار کنید

مثال:

cat.c

# include<stdio.h>
static char cat[6] = "pretty";
void pretty(void)
{
printf("The Cat is %s\n", cat);
}

main.c

# include<stdio.h>
extern void pretty();
int main()
{
pretty();
int cat = 9;
printf("%d\n", cat);

return 0;
}

اگر دو فایل بالا را کامپایل کنیم، در خروجی برنامه اجرا شده متن The cat is pretty و در نهایت عدد ۹ را مشاهده خواهیم نمود. (دقت کنید که در هنگام کامپایل آدرس+نام هر دو فایل را به کامپایلر بدهید) در دو فایل بالا؛ در فایل اولی با نام cat؛ متغیر cat یک رشته است که درون خود واژه pretty را ذخیره کرده است و در فایل دوم متغیر cat یک متغیر از نوع صحیح است که مقدار ۹ را در خود ذخیره کرده است و ما هر دوی آنها در خروجی خط دستوری نمایش داده‌ایم. البته در این قطعه کد از کلیدواژه extern استفاده نمودیم که در مبحث بعدی تشریح خواهد شد؛ اما برای دسترسی به تابع pretty که در فایل دیگری تعریف شده است می‌باشد

متغیرهای خارجی external[ویرایش]

در یک فایل برنامه C، هرگاه یک متغیر که می‌تواند خود، یک تابع باشد؛ را با کلیدواژه extern اعلان کنید، کامپایلر در طول فایل‌های برنامه‌های داده شده به آن، خواهد گشت؛ تا تعریف آن متغیر یا تابع را پیدا کند که زمانی که در فایل اعلان شده از آن استفاده نمودید، آن را به کار گیرد؛ بنابراین هر گاه در یک پروژه تعریف یک متغیر یا تابع در فایل دیگری قرار داشت، در ابتدای برنامه خود نام آن متغیر یا تابع را به همراه نوع داده (و در صورتی که تابع نیز هست به همراه پارامترهای آن) با کلیدواژه extern آن را اعلان کنید و سپس استفاده کنید

مثال:

show.c

# include<stdio.h>
void show(void)
{
    int a = 56;
    printf("%d\n", a);
}

main.c

# include<stdio.h>
extern void show(void);
int main()
{
    show();

    return 0;
}

اگر دو فایل بالا را همزمان به کامپایلر خود بدهید تا کامپایل کند و برنامه کامپایل شده را اجرا کنید عدد ۵۶ را بر روی خروجی خط‌دستوری مشاهده خواهید نمود. در فایل اول یعنی show، تابع show را تعریف نمودیم تا با تعریف متغیر a در خود و دادن مقدار ۵۶ به آن، مقدار متغیر را نمایش دهد. در فایل اصلی خود، یعنی main تابع show را با کلیدواژه extern اعلان نمودیم؛ بدین معنا که تابع در فایل دیگری تعریف شده است. سپس آن را داخل تابع main، احضار نمودیم. دقت کنید که در پروژه‌های خیلی بزرگ علاوه بر فایل‌های متعدد؛ این فایل‌های برنامه داخل فولدرهای بسیار متعددی نیز می‌باشد که برای ضمیمه کردن فایل‌های مورد نیاز در هر فایل برنامه، آنها را با دستور مستقیم include اما با دابل کوت " باز و بسته مثلاً:

#include "show.c"

در ابتدای برنامه خود مثل main.c ضمیمه می‌کنیم که در صورتی که در فولدر دیگری است باید آن را با آدرس دهی کامل و یا با یک اسلش / و نام آن فولدر و در نهایت نام فایل برنامه (به اصطلاح ماژول) یا فایل‌های برنامه را ضمیمه کنیم (البته این نوع نوشتن در محیط IDEها است و برای پیوند زدن - linkage - بین فایل‌های برنامه خود باید آنها را با دستور include در یکدیگر ضمیمه کنید؛ البته واضح است که تنها آن فایل‌هایی که می‌خواهید از تابع‌ها و داده‌های موجود در آنها استفاده کنید)

مثال دوم:

value.c

int g = 91;

main.c

# include<stdio.h>

extern int g;
int main()
{
    printf("%d\n", g);

    return 0;
}

در مثال بالا متغیر g، در فایل value تعریف شده است و در فایل اصلی آن را با کلیدواژه extern اعلان نموده و سپس از آن استفاده نموده‌ایم که در صورت کامپایل و اجرا عدد ۹۱ را در خروجی خط‌دستوری نمایش خواهد داد. دقت کنید که اگر از کلیدواژه extern داخل یک تابع استفاده کنید، مجاز نیستید تا به آن مقداری اختصاص بدهید؛ اما اگر خارج از تابع باشد می‌توانید به آن مقداری بدهید و در این صورت متغیر تعریف می‌شود و می‌توانید در فایل برنامه فعلی از آن استفاده کنید. دقت کنید که شما نمی‌توانید یک متغیر را همزمان extern و static اعلان یا تعریف کنید. همچنین اگر متغیر محلی باشد و در صورت خارجی بودن و به اصطلاح پیوند خارجی داشتن، می‌توانید آن را داخل تابع با کلیدواژه auto محلی کنید و آن را جدا کنید و اگر متغیری سراسری static باشد، پیوند آن با فایل‌های دیگر از بین می‌رود و همان طور که در بحث متغیرهای ایستا گفتیم، اختصاصی می‌شود؛ بنابراین دسترسی به آن حتی با اعلان کردن آن با کمک کلیدواژه extern میسر نمی‌باشد

متغیرهای ثبتی register[ویرایش]

از کلیدواژه register، تنها مجاز هستید تا در بدنه تابع (داخل آکولادهای آن) برای داده‌ها استفاده کنید که در صورت استفاده ممکن است کامپایلر متغیر و داده شما را در برنامه خروجی بر روی رجسیترهای CPU ثبت کند. رجیسترهای CPU، حافظه نهان یا همان cache (کش) واحد پردازنده مرکزی کامپیوتر هستند که دسترسی به آنها برای پردازشگرها، بسیار راحت‌تر و سریع‌تر از حافظه موقت کامپیوتر (رم RAM) می‌باشد. گفتیم: ممکن است! چرا که استاندارد خاصی برای آن وجود ندارد و هر نویسنده به‌کارگیرنده زبان C آن را به صورت دلخواه نوشته است؛ بنابراین در مواقعی ممکن است کامپایلر کلیدواژه register را نادیده بگیرد و ممکن است بدون نوشتن register آن را داخل کش CPU ثبت کند. عموماً در برنامه‌های سطح پائین مثل کرنل و ماژول‌های کرنل (در سیستم عاملی مثل لینوکس) یا درایورها (که در سیسم عامل‌های ویندوز مایکروسافت، متدوال هستند) کلیدواژه، ترتیب اثر داده می‌شود و همچنین در متغیرهایی که نقش شمارنده را دارند و به هر نحوی ممکن است نیاز به دسترسی سریع‌تر داشته باشد؛ کامپایلر آن را در کش، ثبت می‌کند. برای اطلاعات دقیق‌تر به راهنمای کامپایلر خود مراجعه کنید

مثال:

# include<stdio.h>

int main()
{
    register int i;
    for(i=0; i<6; i++)
    {
    printf("%d\n", i);
    }

    return 0;
}

دقت کنید : از آنجایی که متغیرهای register در کش CPU ذخیره می‌شوند؛ دسترسی به آنها از طریق یک اشاره‌گر و یا عملگر آدرس (یعنی &) امکان‌پذیر نیست و امکان آن وجود ندارد و در صورتی که کامپایلر از شما خطا نگیرد، برنامه شما دچار اختلال خواهد شد

متغیرهای فرّار volatile[ویرایش]

در بحث اشاره‌گرها کمی به مبحث متغیرهای فرّار پرداختیم. مبحث کاملاً ساده‌ای است. زمانی که می‌خواهید برنامه‌نویسی سطح پائین انجام بدهید و در محل ذخیره‌های داده‌ها در سخت‌افزار، داده‌ای را ذخیره کنید؛ اگر لازم است که دسترسی به آن توسط سخت‌افزار صورت بپذیرد و ممکن است در صورتی که سخت‌افزار بخواهد آن حافظه را تغییر بدهد و شما آن را volatile یا فرار تعریف نکرده باشید، اختلال نرم‌افزاری رخ بدهد باید آن را فرار تعریف کنید. همچنین در نوشتن برنامه‌هایی مثل RAM Editor یا در هر جایی که بخواهید دسترسی پیدا کنید به خانه‌های خاصی از حافظه موقت؛ مخصوصاً خانه‌های ابتدایی باشد و بخواهید مقدار آن را تغییر بدهید، بهتر است آن را volatile تعریف کنید تا در صورتی که با سیستم‌عامل یا سخت‌افزار تداخل ایجاد می‌کند؛ آنها خود به خود آن را اصلاح کنند. کلیدواژه volatile را می‌توانید پیش از نوع داده یا پس از نوع داده و پیش از شناسه بنویسید. این کلیدواژه در برنامه‌نویسی‌های سطح پائین و در اشاره‌گرها به خانه‌های خاصی از حافظه موقت و همین طور برخی از تابع‌های کتابخانه‌ای که مربوط به پردازش سیگنال‌ها می‌شود؛ کاربرد زیادی دارد

مثال:

volatile int *ptr = (int *)0x1af4e6;

_______________

نکته : در استاندارد C11 نوع جدیدی از کلاس ذخیره با نام Thread_local_ در فایل سرآیند threads.h تعریف شده است که در مبحث مربوطه ( یعنی فایل سرآیند threads ) به آن می‌پردازیم