Bilgisayarda bulunan ve RAM (Random Access Memory, Rastgele Erişilebilir Bellek) adı verilen bellekler, bilgisayarda geçici bilgilerin depolanması için yer alan ana belleklerdir. Bu bellekler aslında o anda sistemde çalışan işletim sistemini ve bu işletim sisteminde yer alan programları tutmak için vardır. Ancak onların boyutları bilgisayarda bulunan ikincil depolama birimleri ile karşılaştırıldığında (SSD, HDD, DVD vb.) oldukça küçük kalır. Ancak hız bakımından ikincil depolama birimlerinden oldukça üstündürler. Bu farklardan ziyade bizim bu yazımızı daha çok ilgilendiren bir fark vardır ki o da ana belleklerin verileri kalıcı olarak tutmamasıdır.
Evet, RAM’ler üzerinde yer alan bilgiler sonsuza kadar tutulmazlar, bilgisayara güç verilmesinden itibaren içlerine belli bilgiler yerleştirilir ve bilgisayarın çalışması süresince bu bilgiler sürekli değişiklik gösterir. En son bilgisayar kapatılırken ise bu bilgiler tamamen silinir. Zaten bu belleklerin boyutları bilgisayarda o anda yapabileceğiniz işin büyüklüğünü de belirler. Bir bilgisayarda açabileceğiniz program sayısı aslında bu belleklerin kapasitesi ile sınırlandırılır. Zaten genellikle bir bilgisayarda her gün 100 programı aynı anda kullanmazsınız (arkaplanda çalışan sistem programları hariç). İşiniz olduğunda bir programı açar ve işiniz bittiğinde de onu genellikle kapatırsınız.
İşte belleğin bu dinamik yapısı nedeniyle programlardaki bilgiler de bellekte sonsuza kadar tutulmazlar. Örneğin C dilinde oluşturduğunuz her bir nesne için bellekte bir yer ayrılırken, artık ihtiyaç duyulmayan nesneler ise bellekten silinmektedir. C dilinde bir nesnenin programın çalışması sırasında bellekte durduğu süreye (duration) o nesnenin ömrü (lifetime) adı verilir. C dilinde nesneler ömürleri dahilinde kullanılabilirler. Ömrünü tamamlamış olan nesneleri kullanmaya çalışmak tanımsız davranışa (undefined behavior) neden olur. C dilinde her nesne, onun ömrünü belirleyen depolama süresi (storage duration) özelliğine sahiptir. Ömür ve depolama süresi kavramları birbirlerinin yerine kullanılabilirler. C dilinde nesneler ömür bakımından 4 gruba ayrılırlar:
- Otomatik Ömür (Automatic Storage Duration): Bildirildikleri blokta, bildirildikleri yerde ömürleri başlayıp bildirildikleri bloğun sonunda ömürleri sonlanan nesnelerdir. Örneğin bir fonksiyon içinde tanımlanan yerel değişkenler bu tip bir ömre sahiptirler.
- Statik Ömür (Static Storage Duration): Programın çalışmaya başlaması ile bellekte yer edinip programın sonlanmasıyla bellekten silinen nesnelerdir.
- Thread Ömrü (Thread Storage Duration): Ömrü bir Thread’in çalışmaya başlamasından sonlanana kadar olan ve o Thread’e özel olan nesnelerdir. Bu ömür C11 standartları ile dile eklenmiştir. Bu ömre sahip olan nesnelerin incelenmesi bu yazının kapsamı dışındadır.
- Dinamik Ömür (Dynamic/Allocated Storage Duration): İsteğe göre bellekten yer verilebilen ve bellekten silinebilen nesnelerdir. Bu ömre sahip olan nesnelerin incelenmesi bu yazının kapsamı dışındadır.
Otomatik ömre sahip olan nesneler, programın belli bir anında bellekteki yerini alıp o nesnenin faaliyet alanının sonunda bellekten otomatik olarak silinen nesnelerdir. Örneğin geçen yazıda gördüğümüz ve blok faaliyet alanına uyan sıradan yerel değişkenler otomatik ömürlüdür. Statik ömre sahip olan nesneler ise programın çalışmaya başlaması anında bellekteki yerlerini alırlar ve program sonlanırken bellekten silinirler. Yani programın bellekteki varlığı aslında onların da varlığını belirler. Yine geçen yazıda bahsettiğim global değişkenler, ileride bahsedeceğim String’ler ve birazdan bahsedeceğim statik yerel değişkeneler bu ömre sahiptirler. Şimdi aşağıdaki örneği inceleyelim:
double global; /* global: statik ömürlü */
void func(int a, int b) /* a, b: otomatik ömürlü */
{
int c = 5; /* c: otomatik ömürlü */
} /* Programın çalışması bu satıra geldiğinde a, b, c nesneleri bellekten silinir */
Yukarıdaki örnekten görebildiğiniz gibi a ve b parametre değişkenleri ile c yerel değişkeni otomatik ömürlü nesnelerdir. Yani bulundukları bloğun sonunda bellekten otomatik olarak silinirler. Ancak global değişkeni statik ömürlü bir nesne olduğundan, fonksiyon çalışmasını sonlandırsa bile bellekte var olmaya devam edecektir. Ancak program sonlanırken bellekten silinecektir. Bu arada yine geçen yazıda bahsettiğim gibi global değişkenlere ilk değer vermeseniz bile otomatik olarak onlara bir ilk değer verilmesi garanti altındadır.
C dilinde nesnelerin temelde 3 özelliği bulunmaktadır. Bu 3 özellik nesnelere derleme, bağlama ve çalışma zamanında nasıl davranılacağını belirler. Bu 3 özellikten ikisi olan faaliyet alanı ve ömür kavramlarından bahsettim. Şimdi de son kavram olan bağlantı özelliğinden biraz bahsedip esas konumuz olan yer belirleyicilerine geçelim. Nesnelerin bağlantı (linkage) özelliği, bir nesneye bir başka faaliyet alanından veya modülden erişimin kuralını belirleyen bir özelliktir. Aynı isimli değişkenler birden fazla faaliyet alanında tanımlanırsa, normalde bu değişkenlerin her biri için bellekte ayrı yer ayırma işlemi yapılır. Ancak bir isme farklı faaliyet alanlarından erişmek istiyorsak, o zaman o nesnenin o faaliyet alanlarında bir kopyasının (instance) oluşturulması gerekir. C standartlarına göre nesneler bağlantı özelliği açısından 3 gruba ayrılırlar:
- Bağlantısız (No Linkage) Nesneler: Nesneye sadece tanımlandığı modülün belli kısımlarından erişilebiliyorsa, ancak her yerinden erişilemiyorsa bu nesne bağlantısız bir nesne olur.
- İç Bağlantılı (Internal Linkage) Nesneler: Nesneye tanımlandığı modülün herhangi bir yerinden erişilebiliyorsa, ancak diğer modüllerden erişilemiyorsa bu nesne iç bağlantılı bir nesne olur.
- Dış Bağlantılı (External Linkage) Nesneler: Nesneye hem tanımlandığı modülden hem de tanımlandığı modül dışında olan diğer modüllerden erişilebiliyorsa bu nesne dış bağlantılı bir nesne olur.
Yine bağlantılarına göre nesnelere örnek vermek gerekirse; bir fonksiyonda yer alan yerel değişkenler ve parametre değişkenleri bağlantısız nesne sınıfına girerler. Global değişkenler ise hem tanımlandıkları modülün her yerinden hem de diğer modüllerden erişilebilir durumdadırlar (diğer modüllerden nasıl erişeceğimizi birazdan anlatacağım). Bu durumda global değişkenler dış bağlantıya sahiptir diyebiliriz. Şimdiye kadar anlattıklarım aslında nesnelerin varsayılan durumlarını programın farklı yerlerinde kullanılması üzerineydi. Ancak bir nesnenin ömür ve bağlantı özelliklerini çeşitli anahtar kelimeler ile değiştirmek mümkündür. Nesnelerin ömür ve bağlantı özellikleri başta olmak üzere, çeşitli özellikleri hakkında derleyiciye bilgi veren anahtar kelimelere Yer Belirleyiciler veya Depolama Sınıfı Belirleyicileri (Storage-Class Specifiers) adı verilmektedir.
C11 standartlarından önce C dilinde 4 adet yer belirleyici bulunmaktaydı; auto, register, static ve extern. C11 ile dile _Thread_local isimli yeni bir yer belirleyici eklenmiştir. Bu yer belirleyicilerin hepsi birer anahtar kelimedir. Yer belirleyiciler değişken, parametre veya fonksiyonların bildirimlerinde yer alırlar. Bildirimlerde en fazla 1 yer belirleyici kullanılabilir. Birden fazla yer belirleyici aynı bildirimde kullanılamaz. Ancak _Thread_local bu kuralın dışında kalır. _Thread_local ile static veya extern yer belirleyicilerinden birini kullanmanıza imkan vardır. Bildirimlerde yer belirleyicisi, tür belirleyicisi ve tür bilgisi belirten anahtar kelimlerin herhangi bir yazım sırası yoktur. Örneğin static yer belirleyicisi ile bildirilen ve const olarak nitelenen bir işaretsiz (unsigned) int değişkeni için yapılan aşağıdaki tüm bildirimler aynı şeyi ifade ederler:
static const unsigned int number; const static unsigned int number; unsigned static const int number; int const static unsigned number; unsigned int static const number;
Yer belirleyicilerin nesnelerin ömür ve bağlantı özelliklerini değiştirdiğini zaten söylemiştik. Bunun yanında değişkenlerin nerede saklanacağını derleyiciye bildirebilirler. Ayrıca her yer belirleyici her nesne türü ile beraber kullanılamaz. Bununla ilgili belli kurallar vardır. Şimdi yer belirleyicilerini ve onların çeşitli özelliklerini bir tabloda özetleyelim:
| Yer Belirleyicisi | Ömür | Bağlantı | Depolama Alanı | Kullanım Alanı |
|---|---|---|---|---|
| auto | Otomatik Ömür | Bağlantısız | Ana Bellek (Stack) | Sadece yerel değişkenlerde kullanılabilir. |
| static | Statik Ömür | İç Bağlantılı | Ana Bellek (Data Segmenti) | Yerel ve global değişkenler ile dosya faaliyet alanındaki fonksiyonlarla kullanılabilir. |
| register | Otomatik Ömür | Bağlantısız | CPU Yazmaçları | Yerel değişkenler ve parametre değişkenlerinde kullanılabilir. |
| extern | Statik Ömür | Dış Bağlantılı | Ana Bellek (Data Segmenti) | Yerel ve global değişkenler ile her türlü fonksiyon bildirimleri ile kullanılabilir. |
| _Thread_local | Thread Ömrü | static veya extern ile kullanımına bağlı | Ana Bellek (Thread) | Sadece yerel değişkenlerde kullanılabilir. |
Bu yer belirleyicilerini tek tek anlatmaya başlamadan önce, herhangi bir yer belirleyicisi olmadan bildirilen nesneler için şu kuralların bulunduğunu da söyleyelim:
- Tüm fonksiyonlar varsayılan olarak extern yer belirleyicisini kullanır.
- Dosya faaliyet alanında bildirilen tüm nesneler varsayılan olarak extern yer belirleyicisini kullanır.
- Blok faaliyet alanında yer alan tüm nesneler varsayılan olarak auto yer belirleyicisini kullanır.
Şimdi en basit ve belki de C dilinin en gereksiz yer belirleyicisi ile başlayalım. auto yer belirleyicisi bir nesnenin otomatik ömre sahip olması ve bağlanıtısız bir nesne olması gerektiğini derleyiciye vurgulamaktadır. Bu yer belirleyicisi sadece yerel değişkenler ile kullanılabildiğinden ve herhangi bir fonksiyon bildirimi ve onun parametre değişkenleri ile birlikte kullanılamadığından, aslında onun kullanımı oldukça gereksizdir. Çünkü zaten blok faaliyet alanında yer alan yerel değişkenler, varsayılan olarak otomatik ömre sahip olan bağlantısız değişkenlerdir. Bununla birlikte C++ dilinde bu anahtar kelimenin anlamı C++11 standartları ile tamamen değiştirilmiştir. Bu nedenle bu yer belirleyicisi ile ilgili küçük bir örnek verip bir diğerine geçeceğim:
int testFunction()
{
auto int number1 = 5;
int number2 = 6; /* number1 ile aynı özelliklere sahiptir. */
return number1 + number2;
}
register yer belirleyicisi ise bir değişkenin ana bellekte değil de işlemci yazmaçlarında (registers) tutulması gerektiğini derleyiciye bildiren bir belirleyicidir. Peki bunun anlamı nedir? Programdaki nesnelerimizin RAM’de tutulduğunu söylemiştik. İşlemcinin bu nesneleri işleyebilmesi için öncelikle onları kendi üzerinde bulunan ve yazmaç (register) olarak adlandırılan geçici bölgelere alması gerekir. Daha sonra makine komutlarını bu yazmaçlardaki bilgilere uygular. Bu nedenle bir bilginin yazmaçlarda tutulması, onun ana bellekte tutulmasından daha hızlı işlenebilmesini sağlar. Ancak yazmaçların boyutları ve adedi kısıtlı olduğundan her bilgiyi de orada tutamayız. İşte bu anahtar kelime derleyiciye yazmaçlarda tutulma isteiğini iletir. Ancak derleyicinin bunu uygulayacağının bir garantisi yoktur. Örnek:
void foo()
{
register int a = 8;
int b = 12;
int c = a + b;
}
Yukarıdaki kodda a değişkeninin yazmaçta tutulacağının bir garantisi yoktur. Bu tamamen kullandığınız derleyiciye ve bilgisayarınızdaki donanıma göre değişiklik gösterecektir. Bir de boyutu int türünün o sistemdeki boyutundan büyük olan nesneler genellikle yazmaçlarda saklanmazlar. Çünkü int türü aslında yazmaç boyutları ile hizalanacak şekilde tasarlanır. Aslında derleyicilerin yukarıdaki kodda yer alan b ve c değişkenlerini de yazmaçta tutmayacağının bir garantisi yoktur. Derleyicilerin bazı optimizasyon çözümleri bu değişkenleri yazmaçta tutmaya zorlayabilir. Bu nedenle bu anahtar kelime günümüzde çok sık kullanılmamaktadır. Hatta C++ tarafında C++17 standartları ile dilde artık eskimiş (deprecated) olarak işaretlenmiştir.
register yer belirleyicisi de tıpkı auto gibi nesneleri otomatik ömürlü ve bağlantısız bir konuma getirir. Aslında onun auto anahtar kelimesinden yazmaçlara yazma konusu dışında bir farkı daha vardır. Bu belirleyici parametre değişkenlerini bildirirken de kullanılabilir. Hatta parametre değişkenleri ile kullanılabilen tek yer belirleyicisidir diyebiliriz (aslında static de kullanılabilir ama onun özel bir anlamı vardır, bu konu bu yazının kapsamı dışındadır). Bunun yannda tıpkı auto gibi bu yer belirleyicisinin dosya faaliyet alanında yer alan değişken ve fonksiyonlar ile kullanımı geçersizdir. Örnek:
register short a; /* Geçersiz Kullanım */
register int foo() { return 5; } /* Geçersiz Kullanım */
void func(register int b) /* Geçerli Kullanım */
{
register int c; /* Geçerli Kullanım */
}
_Thread_local yer belirleyicisi C11 standartları ile dile eklenmiştir ve nesneye Thread ömrü kazandırmaktadır. Yani nesne sadece o Thread’e özel bir hale gelir. Üzerinde bulunduğu Thread oluşturulduğunda o da oluşur ve yok edildiğinde o da yok olur. Bu yer belirleyici yanına static veya extern anahtar kelimelerinden birini alabilir ve bunlar o nesnenin iç veya dış bağlantıya sahip olacağını belirler. Ancak bu anahtar kelimeler o nesnenin ömrüne etki etmez. Bir de bu yer belirleyici yine fonksiyonla veya onların parametreleri ile kullanılamaz. _Thread_local yer belirleyicisinin detaylarını Thread’ler ile ilgili yazılarımda vermeyi düşünüyorum. Şu an için bu bilgilerin yeterli olduğunu düşünüyorum.
Şimdi artık yazımızın en önemli iki yer belirleyicisine geçebiliriz. Öncelikle static belirleyicisi ile başlayalım. static yer belirleyicisi ile nesneleri statik ömre ve iç bağlantıya sahip hale getirmemiz mümkündür. Bu anahtar kelime yerel ve global değişkenlerin bildirimiyle beraber, fonksiyonların bildiriminde de kullanılabilir. Ancak parametre değişkenleri ile kulanılamaz (ileride göreceğimiz karakter dizileri ile ilgili bu konuda bir istisna vardır). Öncelikle yerel değişken ile static yer belirleyicisinin kullanımının ne anlama geldiğini inceleyelim. Bir yerel değişken static anahtar kelimesi ile beraber bildirildiğinde, onun ömrü artık otomatik ömürden statik ömre geçirilir. Yani artık o değişken blok sonunda silinmek yerine program kapanıncaya kadar bellekte kalmaya devam eder. Örnek:
void print_count()
{
static int count = 0;
printf("%d\n", count++);
}
Burada count değişkeni artık statik ömürlü hale gelir. Yani tanımlandığı andan programın kapatıldığı ana kadar bellekteki yerini korur. Ona 0 ile ilk değer vermeseydik bile tıpkı global değişkenlerde olduğu gibi 0 ilk değerini alacaktı. Bu fonksiyonda count değişkenini ekrana basıp daha sonra da değerini 1 arttırıyoruz. Şöyle bir çağrının çıktısı ise aşağıdaki gibi olacaktır:
int main()
{
print_count();
print_count();
print_count();
print_count();
return 0;
}
1
2
3
Peki bu nasıl oldu? Fonksiyon her çağrıldığında bu değişken baştan oluşturulup ona 0 değeri veriliyormuş gibi görünebilir. Ancak durum böyle değildir. Program ilk belleğe yüklenirken zaten bu değişken de bellekte statik alana yüklenmiş olur. Yani biz count_print fonksiyonunu her çağırdığımızda aslında bu satır görmezden gelinir. Bu satır derleme zamanında faaliyet alanı kurallarının geçerli olması için gereklidir. Bu satırdan sonra gelen satırda değişkenin değerini değiştirdiğimizden, bundan sonraki bütün çağrılarda da bu değişim kalıcı olacaktır.
static anahatar kelimesi ile her ne kadar yerel değişkenleri statik ömre yükseltsek de onlar hala blok faaliyet alanı kurallarına uyarlar. Örneğin yukarıdaki main fonksiyonunu şu şekilde yazmamız geçersiz olacak ve derleme zamanında hata alacağız:
int main()
{
print_count();
count = 3; /* Bu faaliyet alanında bu isimde bir değişken yoktur. */
print_count();
print_count();
print_count();
return 0;
}
Daha önceki yazıda bahsettiğimiz gibi farklı faaliyet alanlarındaki değişkenler farklı olarak ele alınırlar. Bu nedenle yukarıdaki kod geçersiz oacaktır. Çünkü main içerisinde herhangi bir count bildirimi yapılmamıştır. Ancak static ile bir bildirim yapılsa bile bu iki count değişkeni aynı değişken olmayacaktır. Buna dikkat etmenizde fayda var. static yer belirleyicisi dosya faaliyet alanındaki değişkenler (global değişkenler) ve fonksiyonlar ile de kullanılabilir. Bu tip nesnelerin varsayılan olarak extern olarak bildirildiğini zaten söylemiştik. static ile bu nesnelerin bağlantı özelliğini dış bağlantıdan iç bağlantıya geçirebiliriz. Bağlantının tam olarak ne işe yaradığını birazdan extern ile açıklayacağım. Şimdi şu örneklere bakalım:
#include <stdio.h>
static int a = 3; /* Geçerli */
static void func1(); /* Geçerli */
static int func2() /* Geçerli */
{
return a;
}
int main()
{
func1();
printf("%d\n", func2());
return 0;
}
void func1()
{
printf("Hello!\n");
}
Yukarıdaki örnekten anlayacağınız gibi sadece fonksiyon prototipinde static anahtar kelimesini kullanmanız yeterlidir. Fonksiyon tanımında buna gerek yoktur, ama kullanmanızda bir sakınca da yoktur. Aynı şekilde bildirim ve tanımı aynı anda yapılan fonksiyonlar ile static anahtar kelimesini kullanabilirsiniz. Dediğim gibi bunların hepsi bu nesneleri iç bağlantılı hale getirir. Onun dışında nesnelerin statik ömürlü olma durumlarını değiştirmez. Ancak blok faaliyet alanında yapılan fonksiyon bildirimlerinde static anahtar kelimesi kullanılamaz. Örnek:
int main()
{
static int test(); /* Geçersiz! */
return 0;
}
Şimdi extern yer belirleyicisine geçelim. Bu yer belirleyicisinin static yer belirleyicisinden tek farkı aslında nesneyi iç bağlantılı değil dış bağlantılı hale getirmektir. Bir de bu yer belirleyicisi yerel ve global değişkenler ile global fonksiyonların dışında, az önce belirttiğim yerel fonksiyon bildirimleri ile de kullanılabilir. Global değişkenler ve fonksiyonlar zaten varsayılan olarak extern olarak ele alınırlar. Ancak global değişkenlerin önüne extern anahtar kelimesinin yazılması ile yazılmaması arasında fark vardır. Global fonksiyon bildirimlerinde ise başına extern yazılması ve yazılmaması arasında bir fark yoktur. Şimdi global değişkenler ile extern kullanımıyla işe başlayalım. Öncelikle elimizde 2 kaynak kod dosyası olsun:
// src1.c int a = 30;
// main.c
#include
int main()
{
printf("%d\n", a);
return 0;
}
Yukarıdaki 2 dosyanın da aynı projede olduğunu varsayalım. Her bir “.c” uzantılı dosya kendi başına derleneceğinden “main.c” dosyası derleme aşamasını geçemeyecektir. Çünkü derleyicinin a isimli bir nesneden henüz haberi yoktur. İşte extern anahtar kelimesi ile derleyiciye bu bilgilendirmeyi yapabiliriz. Yani derleyiciye “Bu değişkenin tanımı bağlama zamanında başka bir dosyadan alınacak!” diye not düşebiliriz. Bunu yapmak için “main.c” dosyasına aşağıdaki gibi a değişkeninin bildirimini eklemeniz gerekmektedir:
// main.c
#include <stdio.h>
extern int a;
int main()
{
printf("%d\n", a);
return 0;
}
Böylelikle derleme zamanında a değişkeninin başka bir kaynak dosyadan alınacağı bildirilecek ve bağlama zamanında a değişkeninin tanımı “src1.c” dosyasından alınacaktır. Sonuç olarak ekrana 30 değeri basılacaktır. Yani bir değişkeni gerçek anlamda bildirmek için başına extern anahtar kelimesini getirmemiz gerekir. Diğer türlü onu tanımlamış oluyoruz. Bir değişken veya fonksiyon için bildirme işlemi birden fazla kez yapılabilirken, tanımlama işlemi yalnızca 1 kez yapılabilir. Bir değişkeni veya fonksiyonu birden fazla kez tanımlarsanız, bu sefer bağlama zamanında hata alırsanız. İşte burada da “main.c” dosyasında a değişkeninin bildirimi yer alırken, onun tanımı “src1.c” dosyasında yer alır. extern anahtar kelimesinin yegane kullanım alanı budur.
Elbette burada bir konuya daha değinmek gerekir; bir değişkeni extern ile bildirirken ona bir ilk değer atarsanız, aslında onu tanımlamış olursunuz. Tanımlamak aslında bildirmenin üst kümesidir. Yani tanımladığınız bir nesneyi aslında bildirmiş de olursunuz. Bunu unutmamak gerekir. Global fonksiyon bildirimleri ile extern kullanmak ve kullanmamak arasında pek bir fark yoktur. Aşağıdaki bildirimler birbirleri ile aynı anlama gelir:
int sum(int a, int b); extern int sum(int a, int b);
Yine “src1.c” isimli kaynak kod dosyasında tanımları yer alan değişkenleri ve fonksiyonları “main.c” dosyasında nasıl kullanabileceğimiz ile ilgili bir örnek yapalım:
// src1.c
int a = 5;
int b = 10;
int operation1(int c)
{
return c * a;
}
int operation2(int c)
{
return c + b;
}
// main.c
#include <stdio.h>
extern int a;
extern int operation1(int c);
int operation2(int c);
int main()
{
a = 30;
int res1 = operation1(10); /* 10 * 30 = 300 */
int res2 = operation2(5); /* 5 + 10 = 15 */
printf("res1=%d, res2=%d\n", res1, res2);
return 0;
}
Gördüğünüz gibi farklı kaynak dosyalarda tanımları yer alan fonksiyonları extern kullanmadan bildirerek de diğer kaynak dosyalarda kullanabilirsiniz. extern yer belirleyicisi ile bildirilmiş ama herhangi bir yerde tanımı olmayan bir nesneyi kullanmaya kalkarsanız, derleme zamanında değil de bağlama zamanında hata alırsınız. Aynı şekilde static yer belirleyicisi ile iç bağlantılı hale getirilmiş bir nesneyi artık extern ile çekmeniz mümkün olmayacaktır. Örnek:
// src1.c
#include <stdio.h>
int a = 5;
static int b = 10;
static int internal_func() { return a + b; }
void external_func() { printf("%d\n", internal_func()); }
// main.c
#include <stdio.h>
extern int a;
extern void external_func();
extern int b;
int main()
{
a = 10;
b = 20; /* Hata! */
external_func();
return 0;
}
Yukarıdaki “main.c” kodunda aslında 12. satırı silerseniz program düzgünce derlenip bağlanır ve ekrana 20 çıktısını basar. Ancak iç bağlantıya sahip olan b değişkenini alıp değiştirmeye veya kullanmaya çalışmak bağlama zamanında hataya neden olacaktır. Aynı şekilde iç bağlantıya sahip internal_func fonksiyonunu da “src1.c” kaynak dosyasından alamazsınız. İşte aslında static ve extern anahtar kelimelerinin yaptığı iç ve dış bağlantı olaylarını böyle özetleyebiliriz. Eğer statik ömürlü bir nesneyi sadece o modülde kullanmak istiyorsanız, onu static anahtar kelimesi ile tanımlamanız gerekir. Eğer bir nesneye dış modüllerden erişim sağlanmasını istiyorsanız da onu (global nesne ise) herhangi bir yer belirleyici olmadan tanımlamanız gerekir. Daha sonra extern ile bu nesnenin diğer modülde bildirimini yapıp onu kullanabilirsiniz.
Bunların dışında yine yer belirleyicisi kategorisine giren typedef adlı bir belirleyici bulunmaktadır. Ancak bu anahtar kelime nesnenin herhangi bir ömür ve bağlantı özelliğini değiştirmez. Onun daha başka bir kullanımı vardır ve biz bu anahtar kelimeyi ileriki yazılarda inceleyeceğiz. Böylelikle yer belirleyiciler konusunu burada noktalıyorum. Aslında yer belirleyicilerinin kullanım senaryolarında çok farklı durumlar ortaya çıkabilir. Ancak ben burada aşırı ayrıntıya girip konuyu farklı yerlere taşımak istemedim. Bu yazıda genel olarak nesnelerin ömür ve bağlantı kavramlarının ne anlama geldiğini ve bunları belirleyen yer belirleyicilerini anlatmaya çalıştım. Yer belirleyicileri ile ilgili ayrıntılı özellikleri ilgili yazılarda anlatacağım.


