C99 standartları ile C diline eklenen en önemli araçlardan biri “compound literal”. Türkçe karşılığı olarak “bileşik sabit” teriminin kullanılmasını öneriyorum.
C’de yazdığımız kodlarda sıklıkla şöyle bir durumla karşılaşıyoruz: Bir diziye, bir yapı (structure) ya da bir birlik (union) nesnesine ihtiyacımız var. Ancak söz konusu nesneyi muhtemelen yalnızca tek bir yerde kullanacağız. Bu durumda isimlendirilmiş bir değişken tanımlamamız gerekiyor. Aşağıdaki koda bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
struct Rect{ double e1, e2; }; void draw_rect(struct Rect); int foo(const int *p); int main() { struct Rect rec = { 1.2, 6.7 }; int a[] = { 2, 6, 7, 1, 3 }; draw_rect(rec); foo(a); } |
main işlevi içinde tanımlanan rec ve a nesnelerinin yalnızca draw_rect ve foo işlevlerine yapılan çağrılarda kullanılmak için tanımlandığını düşünelim. Kod bu niyeti açıkça anlatmadığı için okuyucuyu da yanıltıyor. Ben böyle bir kodu okuduğumda bu nesnelerin kapsamları (scope) içinde tekrar kullanılacaklarını düşünüyorum. Bu tür kodlarda bir başka sorun da “kapsam sızıntısı” (scope leakage) yani bu isimlerin kapsamlarının gereksiz yere geniş tutulması. Bu nesneler bir daha kullanılmayacak olsalar da kapsamları içinde yanlışlıkla isimlerinin yazılması bir kodlama hatasına neden olabilir.
Bir “compound literal” kullanarak isimlendirilmemiş bir dizi, yapı ya da birlik nesnesi oluşturabiliriz:
1 2 3 4 5 6 |
int main() { draw_rect((struct Rect){ 1.2, 6.7 }); foo((int []) { 2, 6, 7, 1, 3 }); } |
İşlevlere gönderdiğimiz argümanlar bileşik sabitler. Aslında struct Rect türünden bir yapı nesnesini ve 5 öğeli bir int diziyi isim vermeden oluşturmuş olduk. Şimdi “compound literal” ifadelerine yönelik sentaksı ayrıntılarıyla ele almaya başlayabiliriz. Önce dizi oluşturan bileşik sabit ifadelerini inceleyelim: Sentaksta yer alması gereken parantez içine dönüşüm türünü (cast type) yazıyoruz. Bu oluşturulacak dizinin türü. Daha sonra küme parantezi içinde oluşturacağımız dizinin öğelerine verdiğimiz ilk değerleri listeliyoruz. Örneğin
1 |
(int [3]){1, 2, 3} |
ifadesi ile 3 öğeli bir int dizi oluşturmuş oluyoruz. Parantez içinde dizinin türünü belirtirken dizinin boyutunu yazabileceğimiz gibi boyut değerinin çıkarımını derleyiciye de bırakabiliyoruz:
1 |
(int []){1, 2, 3, 4, 5, 6} |
Yukarıdaki ifade ile 6 öğeli int bir dizi oluşturduk. Dizinin öğe sayısını belirtir ve öğe sayısından daha az sayıda ilk değer sağlarsak dizinin kalan öğeleri varsayılan değerlerle (tamsayı ve gerçek sayı dizileri için 0, pointer dizileri için NULL pointer) hayata başlıyorlar:
1 |
(double a[20]) {1., 2., 3.} |
ifadesi ile 20 öğeli bir dizi oluşturarak dizinin ilk 3 öğesinin alacağı değerleri belirtmiş olduk. Dizinin kalan 17 öğesi 0. değerleriyle hayata başlayacak. Dizi öğelerine ilk değer verirken yine C99 standartlarıyla dile eklenen “designated initializer” denilen sentaks ile dizinin seçilmiş öğelerine ilk değer verip diğer öğelerini varsayılan değerlerle başlatabiliyoruz:
1 |
(int [100]){[12] = 2, [34] = 4, [67] = 6} |
Yukarıdaki ifade ile 100 öğeli bir dizi oluşturduk. dizinin sırasıyla 12, 34 ve 67 indisli öğelerine ilk değerlerini verdik ve kalan öğelerinin hayata 0 değerleriyle gelmesini sağladık. “designated initializer” kullanılması durumunda yine dizinin boyutunu belirtmek zorunda değiliz:
1 |
(int []){[12] = 2, [34] = 4, [67] = 6} |
Yukarıdaki ifade ile bu kez 68 öğeli bir dizi oluşturmuş olduk. Yapı ya da birlik nesnelerinin “compound literal” biçiminde oluşturulması için kullanılması gereken sentaks da neredeyse aynı. Employee isimli bir yapı türünün bildirildiğini düşünelim:
1 2 3 4 5 |
typedef struct { char name[40]; int id; double wage; }Employee; |
Şimdi bu türden nesnelerin oluşturulmasını sağlayacak bazı “compound literal” ifadeleri yazalım:
1 |
(Employee){"Burhan Koc", 1345, 45.60) |
Yukarıdaki ifade ile Employee türünden bir nesneyi tüm öğelerine ilk değer vererek oluşturduk.
1 |
(Employee){"Furkan Demirci") |
Yukarıdaki ifadede ise oluşturduğumuz Employee nesnesinin yalnızca name isimli öğesine ilk değer verdik. Nesnemizin diğer öğeleri varsayılan değerlerle hayata başlamış oldu.
1 |
(Employee){.id = 7651, .name = "Can Demirci") |
Yukarıdaki ifade de ise oluşturduğumuz Employee nesnesinin seçilmiş öğelerine “designated initializer” sentaksı ile ilk değer verdik. Şimdi de aşağıdaki kodu inceleyelim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
typedef struct { const char *pname; int no; } Student; int main() { Student a[] = { [0].pname = "Metin", [0].no = 13, [5].pname = "Sade", [0].no = 21, [7].pname = "Kagan", [0].no = 34, [9].pname = "Aleyna",[0].no = 77, }; //... a[1] = (Student) { "Seher", 45 }; a[2] = (Student) { "Cihan", 98 }; //... } |
Yukarıdaki kodda main işlevi içinde öğeleri Student türünden boyutu 10 olan bir dizi tanımlanıyor ve dizinin belirlenmiş öğelerine ilk değer veriliyor. Daha sonra dizinin 1 ve 2 indisli öğelerine Student türünden bileşik sabit ifadeleri ile atamalar yapılıyor. Aynı türden yapı nesnelerinin birbirlerine atanabildiğini hatırlayalım.
Compound literal ifadeleri ile oluşturulan nesnelere sabit ifadeleri (constant expressions) ile ilk değer verme zorunluluğu yok:
1 2 3 4 5 6 |
void f(int x, int y, int z) { int *p = (int[]) { x, y, z }; /// } |
Yukarıda tanımlanan func işlevi içinde oluşturulan 3 öğeli int diziye isimlendirilmemiş nesneye işlevin parametre değişkenleri ile ilk değer veriliyor. Diziden adrese dönüşüm (array to pointer conversion) kuralı burada da geçerli.
Compound literal ifadeleri ile tekil (scalar) türlerden de nesneler oluşturmamız mümkün olsa da uygulamalarda böyle bir gereksinim olduğunu düşünmüyorum:
1 2 3 4 5 6 |
void f() { int *ip = &(int) { 10 }; double *dp = &(double) { 12.3 }; // } |
Yukarıdaki örneği yalnızca kodun geçerli olduğunu göstermek için verdim.
“literal” sözcüğü “sabit” anlamında kullanılsa da “compound literals” biçiminde oluşturulan nesnelerin değerlerini değiştirmek tanımlı (defined) davranış niteliğinde:
1 2 3 4 5 6 7 8 9 |
#include <string.h> int main() { Employee *p = &(Employee) { .id = 7651 }; strcpy(p->name, "Nurdan Temiz"); p->wage = 45.80; //... } |
Yukarıdaki örnekte oluşturulan Employee nesnesinin adresi ile p isimli pointer değişkene ilk değer veriliyor. Daha sonraki deyimlerle nesnemizin name ve wage isimli öğelerinin değerlerinin değiştirildiğini görüyorsunuz. Bileşik sabit ifadeleri ile oluşturduğumuz dizileri de değiştirebiliriz:
1 2 3 4 5 6 7 |
int main() { int *p = (int[5]) { 0 }; //... for (int i = 0; i < 5; ++i) p[i] = i; } |
Bu şekilde oluşturulan char türden dizilerde tutulan yazıları da değiştirebiliriz:
1 2 3 4 5 6 |
int main() { char *p = (char[]) { "Beyhan" }; // *p = 'S'; } |
Ancak bir compound literal ifadesi ile const bir nesne de oluşturmamız mümkün:
1 2 3 4 5 6 7 8 |
void display_employee(const Employee *p); int main() { const int *p = (const int[]) { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 }; // display_employee(&(const Employee) { "Nihat Uslu", 7121, 20.20 }); } |
Global kod alanında oluşturulan “compound literal” nesneleri, diğer isimlendirilmiş global nesneler gibi statik ömür (static storage class) kategorisindeler. Blok içinde oluşturulan nesneler ise otomatik ömre (automatic storage class) sahipler:
1 2 3 4 5 6 7 8 9 10 11 12 |
void f() { int *p; extern int func(void); { p = &(int) { func() }; *p = 1; //sorun yok } // p pointer değişkeni dangling durumda. *p = 2; //tanımsız davranış } |
Yukarıda tanımlanan func işlevi içinde oluşturulan içsel blokta oluşturulan int türden otomatik ömürlü nesnemizin adresini p isimli bir pointer değişkene atıyoruz. Otomatik ömürlü nesnenin hayatı oluşturulduğu kapsamı sonlandıran “}” atomunun bulunduğu yerde sona erecek. Bloğun dışındaki kodlar yürütüldüğünde artık nesnemiz hayatta olmadığı için p pointer değişkeni bu durumda geçersiz (dangling) durumda. Şimdi de aşağıdaki koda bakalım:
1 2 3 4 5 6 7 8 9 10 11 |
typedef struct { int mx, my; }Point; void drawpixel(Point); void drawline() { for (int i = 0; i < 10; ++i) drawpixel((Point) {i, i}); } |
Yukarıdaki kodda drawline işlevinin tanımında yer alan for döngüsünün her turunda yeni bir Point nesnesi oluşturuluyor. Böylece işlev (0,0) ve (9, 9) noktalarını birleştiren bir doğru çiziyor.
Compound literal ifadeleri sabit ifadesi kategorisinde olmadıkları için normal olarak statik ömürlü bir nesneye bir compound literal ifadesi ile ilk değer vermemiz geçerli değil:
1 2 3 4 5 6 7 8 9 10 11 |
struct Point { int mx, my; }; void foo() { static struct Point p = (struct Point) { 1, 3 }; static int y[] = (int[]) { 1, 2, 3 }; static int z[] = (int[3]) { 1 }; //... } |
Yukarıdaki kodda foo içinde yapılan tanımlamaların hiçbiri geçerli değil. Ancak bir GNU eklentisiyle GCC derleyicisinde bu mümkün kılınmış. Bu eklenti kullanıldığında yukarıdaki tanımlamalar
aşağıdaki gibi bir kodla aynı anlama geliyor:
1 2 3 4 5 6 7 8 9 10 11 |
struct Point { int mx, my; }; void foo() { static struct Point p = { 1, 3 }; static int y[] = { 1, 2, 3 }; static int z[] = { 1, 0, 0 }; //... } |
Bileşik sabit ifadeleri C++ dilinin sentaksında yer almıyor. Ancak başta GCC olmak üzere birçok C++ derleyicisi bu özelliği bir eklenti (extension) olarak kullanıma sunuyor.