C programlama dilinde değişkenlerin ömrünü ve görünürlüğünü düzenleyen dört adet storage class specifier (typedef’i saymazsak) vardır.
Auto
Öncelikle altını çizmek gerekir C++11’den itibaren auto keywordü C ve C++ için aynı anlama gelmiyor. Bu yazı C’ye odaklandığı için burdan devam edelim.
Bunun yanında C’de auto keywordu aslında gereksizdir. Çünkü kullanılmasına izin verilen yerlerde zaten default olarak kullanılan storage class’tır. auto kelimesi bir fonksiyon içinde ya da kod bloğu içinde kullanılabilir. Global değişken olarak kullanılamaz, compile hatası alınır.
Auto, değişkenin ömrünün kod bloğu bittiğinde biteceğini tanımlar. Bu yönüyle static’in zıttıdır denebilir.
Eğer değişken ismi kullanılmadan auto kelimesi kullanılırsa, değişkenin int olduğu varsayılır.
// auto int m = 2; /* compile hatası; auto, global değişkenler için kullanılamaz. */
int main()
{
int x = 5;
auto int y = 6;
auto z = 10; /* int olarak varsayılır */
}
Register
Auto ile çok benzerdir, fonksiyon ya da kod bloğu içinde kullanılması gerekir. Register kelimesi, eğer mümkünse değişkenin bir register üzerinde tutulmasını ister. Register’lara erişmek RAM’den daha hızlı olduğu için register değişkenler daha hızlı çalışır. Ancak register kelimesini kullanmak değişkenin register üzerinde tutalacağını garanti etmez. Kullanılan donanımın özelliklerine göre gerçekleşebilir ya da gerçekleşmeyip normal bir değişken gibi, auto, olarak da derlenebilir.
Modern compilerların çoğu keyword kullanılmasa bile sürekli çağırılan değişkenleri registerda tutarak yazılımı optimize eder. Hatta register keywordunu hiç kullanmayıp kontrolü tamamen compilere vermenin iyi bir pratik olduğunu iddia eden yaklaşımlar da vardır.
Registerların dezavantajı değişken register üzerinde tutulduğu için memory adreslerinin kullanılamamasıdır.
// register int x = 10; /* compile hatası, global register olamaz. */
int main()
{
int i = 5;
register int j = 10;
// int* k = &j; /* compile hatası, register'ın adresi alınamaz */
register int* a = &i; /* ok, pointer register üzerinde tutulabilir. */
}
Son olarak hatırlatmakta fayda var ki C++ için C++17’ye kadar register keywordu auto olarak kabul ediliyordu, C++17’den sonra deprecated oldu [kaynak].
Static
Statik Değişkenler
Statik değişkenlerin ömrü register ve autonun aksine kod bloğu ile sınırlı değildir. Tüm program boyunca hafızada yer almayı sürdürürler.
Statik değişkenler sadece bir kez initialize edilir. Daha sonraki initialize girişimleri görmezden gelinir.
#include<stdio.h>
int increase()
{
static int count = 0; /* sadece ilkinde initialize edilir */
count++;
return count;
}
int main()
{
printf("%d ", increase());
printf("%d ", increase());
return 0;
}
// output: 1 2
Statik değişkenler hafıza üzerinde stackte değil data segmentte tutulur.
Initialize edilmemiş static değişkenler sıfır ile, pointer ise NULL ile initialize edilir. Auto değişkenler ise otomatik olarak initialize edilmez ve initialize edilmemiş auto değişkenleri kullanmak indeterminate olarak tanımlanmıştır. Yani kullanmamak gerekir.
#include<stdio.h>
int glob;
static int glob_sta;
int main()
{
static int local_sta;
printf("%d ", glob);
printf("%d ", glob_sta);
printf("%d ", local_sta);
return 0;
}
// output: 0 0 0
Static değişkenler constant literal ile initialize edilebilir. Aksi durumda derleme hatası alınır. (C++ ile derlenir ve düzgün biçimde çalışır.)
#include<stdio.h>
int init(void)
{
return 2;
}
int main()
{
static int i = init(); /* compile hatası */
printf("%d", i);
return 0;
}
Struct içerisinde static bir member olamaz. (C++’ta olabilir.) sebebi
Statik Fonksiyonlar
Öncelikle C’deki static fonksiyonların C++’taki static fonksiyonlardan çok farklı olduğunu belirtmek gerekir. C’de bütün fonksiyonlar default olarak extern’dür.
Static kelimesi fonksiyonlar için kullanıldığında sadece fonksiyonun görünürlüğünü etkiler. Static fonksiyonlar sadece tanımlandığı dosya içinden (daha doğru bir ifade ile translation unit) erişilebilir ancak static olmayan fonksiyonlar diğer dosyalar tarafından da erişilebilir. Aşağıdaki örnek şurdan alınmıştır.
// helper_file.c
int f1(int); /* prototype */
static int f2(int); /* prototype */
int f1(int foo) {
return f2(foo); /* ok, f2 is in the same translation unit */
/* (basically same .c file) as f1 */
}
int f2(int foo) {
return 42 + foo;
}
// main.c
int f1(int); /* prototype */
int f2(int); /* prototype */
int main(void) {
f1(10); /* ok, f1 is visible to the linker */
f2(12); /* nope, f2 is not visible to the linker */
return 0;
}
Extern
Extern Değişkenler
Extern keywordu, compiler’a değişken için hafızada yer ayrılmasına gerek olmadığını, zaten bir yerlerde bu değişkenin define edildiğini ve linker’ın define edildiği yeri bulup bağlantıyı kuracağını söyler.
Öncesinde declare, define konusuna açıklık getirelim [kaynak]. Declaration ile hafızada yer ayrılmaz ama definiton ile hafızada yer ayrılır. Extern, değişkeni declare eder ancak definition’ın başka bir yerde olduğunu umar.
// aşağıdakiler declaration'a örnektir
extern int bar;
extern int g(int, int);
double f(int, double); // default extern
// aşağıdakiler definition'a örnektir
int bar;
int g(int a, int b) {return a*b;}
double f(int i, double d) {return i+d;}
Örnek olarak bir dosyadan diğerine veri aktarmak için kullanılabilir.
// dosya1.c
int global_int = 1;
// dosya2.c
extern int global_int;
printf("%d", global_int);
Extern Fonksiyonlar
C’de tüm fonksiyonlar default olarak extern’dür. Bu, diğer dosyalar (translation unit) tarafından görülebileceğini ifade eder. İstenirse static keywordu ile encapsulation sağlamak için external linkage bağlantısı olmayan fonksiyonlar yaratılabilir. Static fonksiyonlar kısmındaki örneği inceleyebilirsiniz.