> Fonksiyon Göstericileri: Aslında fonksiyonlar makine komutlarından oluşmaktadır. Fonksiyonların makine komutları ardışıl bir biçimde bellekte bulunmaktadır. Bir fonksiyonu çağırmak ise aslında o fonksiyonun başlangıcındaki komutun bulunduğu yere akışın aktarılmasıdır. Makine dillerinde fonksiyon çağırma işlemi için genellikle CALL biçiminde isimlendirilen makine komutları bulundurulmuştur. Bu makine komutları çalıştırılacak fonksiyonun başlangıç adresini operand olarak almaktadır. Örneğin: "CALL address" Pekiyi CALL makine komutuyla goto işlemini yapan JMP (bu komut BRANCH olarak da isimlendirilmektedir) komutları arasındaki fark nedir? İşte CALL makine komutları geri dönüş imkanını verirken JMP komutları geri dönüş imkanını vermemektedir. Pekiyi geri dönüş nasıl olmaktadır? İşte CALL makine komutu adrese dallanmadan önce sonraki komutun adresini stack denilen alamda saklar sonra adrese dallanır. Fonksiyonun sonunda bir RET makine komutu bulunur. Bu RET makime komutu da stack'te saklanan adrese dallanır. Yani geri dönüşün mümkün olmasının sebebi aslında geri dönüş adresinin saklanmış olmasındadır. Halbuki JMP komutları geri dönüş adreslerini saklamamaktadır. Biz fonksiyonları ardışıl makşine komutları olarak düşünebiliriz. Onları çağırmak için onların yalnızca başlangıç adreslerini bilmemiz yeterli olmaktadır. İşte fonksiyon adreslerini tutan özel göstericilere "fonksiyon göstericileri (pointer to function)" denilmektedir. Bir fonksiyon göstericisi tanımlamanın genel biçimi şöyledir: (*)([parametrik yapı]); Burada önemli olan noktalardan biri * ve gösterici isminin parantez içerisinde olmasıdır. Örneğin: int (*pf1)(int); double (*pf2)(int, int); int (*pf3)(struct stat *); Bir fonksiyon göstericisine herhangi bir fonksiyonun adresi atanamaz. Geri dönüş değeri ve parametre türleri belli biçimde olan fonksiyonların adresleri atanabilmektedir. Yukarıdaki örnekte pf1 göstericisine "geri dönüş değeri int türden olan parametresi int türdne olan" herhangi bir fonksiyonun adresi atanabilir. pf2 göstericisine "geri dönüş değeri double türden olan ve parametreleri int ve int türden olan" fonksiyonların adresleri atanabilmektedir. pf3 göstericisine ise "geri dönüş değeri int türden olan ve parametresi struct stat türünden gösterici olan" fonksiyonların adresleri ataanbilir. Fonksiyon göstericisi bildiriminde parametre parantezinin içerisine yalnızca parametrelerin türleri yazılabilir ya da istenirse onlara ilişkin isimler de yazılabilir. Tabii bu isimlerin herhangi isimlerle uyuşması gerekemektedir. Bu isimler okunabilirliği artırmak için bulundurulabilir. Ancak C programcıları genellikle parametre isimlerini belirtmezler. Örneğin: int (*pf)(int, int); int (*pf)(int a, int b); Bu iki prototip arasında hiçbir farklılık yoktur. Fonksiyon gösterici bildiriminde dekleratördeki parantezler kullanılmazsa bildirimin tamamne başka bir anlam ifade edeceğine dikkat ediniz. Örneğin: int (*pf)(int, int); Burada geri dönüş değeri int olan ve parametreleri int, int olan fonksiyonların adreslerini tutabielcek bir fonksiyon göstericisi tanımlanmıştır. Fakat örneğin: int *pf(int, int); Bu bildirim ise "geri dönüş değeri int * olan parametreleri int, int olan pf isimli bir fonksiyonun" prototip bildirimidir. Örneğin: int (*pf)(int, int); Buradaki fonksiyon gösterici bildiriminde dekleratör (*pf)(int, int) biçimindedir. Fonskiyon göstericisi bildiriminde C'de parametre parantezlerinin içinin boş bırakılmasıyla içine void yazılması arasında önemli bir farklılık vardır. Eğer bildirimde parametre parantezlerinin içi boş bırakılırsa bu durum "fonksiyon göstericisine geri dönüş değeri uyumu korunmak üzere herhangi bir parametrik yapıya sahip fonksiyonların adreslerinin atanabileceği" anlamına gelmektedir. Örneğin: int (*pf1)(); Burada pf1 göstericisine geri dönüş değeri int türden olmak koşuluyla parametrik yapısı nasıl olursa olsun her fonksiyonun adresi atanabilmektedir. Halbuki örneğin: int (*pf2)(void); Burada pf2 göstericisine geri dönüş değeri int olan ve parametresi olmayan (ya da void olan da diyebiliriz) fonksiyonların adresleri atanabilmektedir. Ancak C++'ta yukarıdaki iki biçim tamamen aynı anlama gelmektedir. Bu iki biçimin her ikisi de C++'ta geri dönüş değeri int olan parametresi olmayan fonksiyonların adreslerini atayabileceğimiz göstericilere ilişkindir. C'de tür dönüştürmelerinde kullanabileceğimiz türlerin sembolik isimleri vardır. Çrneğin: int a; int *pi; int b[10]; int *c[20]; Burada a "int" türden, pi "int *" türünden, b "int[10]" türünden ve c de "int *[20]" türündendir. Bir fonksiyon göstericisinin bu biçimdeki sembolik tür isimleri belirtilirken yine * atomu parantez içerisine alınır. Örneğin: int (*pf)(void); Burada pf göstericisi "int (*)(void)" türündendir. Diğer yandan C'de bir fonksiyonun yalnızca ismi o fonksiyonun başlangıç adresi anlamına gelmektedir. Örneğin: int foo(void) { ... } Burada yalnızca foo ismi bu fonksiyonun bellekteki başlangıç adresini belirtir. foo ismi bir nesne belirtmemektedir. Tıpkı dizi isimlerinde olduğu gibi bir sağ taraf değeri belirtmektedir. Zaten C'de fonksiyonların çağrılmasını sağlayan operatör (...) operatörüdür. Bu durumda foo ifadesi ile foo() ifadesi tamamen farklıdır. foo ifadesi foo fonksiyonunun bellekteki başlangıç adresi anlamına gelmektedir. Ancak foo() ifadesinde bu adreste bulunan fonksiyon çağrılmış ve int değeri elde edilmiştir. Bu durumda foo ifadesi "int (*)(void)" türünden, foo() ifadesi ise "int" türdendir. Fonksiyon çağırma operatörü aslında "beli bir adresten başlayan fonksiyon kodlarının çalıştırılması" işlemini yapar. Örneğin: foo(); Burada aslında "foo adresinden başlayan fonksiyon kodları" çalıştırılmaktadır. Bu durumda biz bir fonksiyon göstericisine bir fonksiyonun adresini atamak istediğimizde fonksiyonun yalnızca ismini atamalıyız. Örneğin: int foo(void) { .... } ... int (*pf)(void); pf = foo; /* geçerli */ Atama işlemini şöyle yapamayız: pf = foo(); /* geçersiz! */ Burada pf değişkenine bir fonksiyon adresi değil düz bir int değer atanmaya çalışılmaktadır. Tabii biz bir fonksiyon göstericisine uyumlu bir fonksiyon adresi ile ilkdeğer de verebiliriz. Örneğin: int (*pf)(void) = foo; Örneğin: int a, *pi, (*pf)(int); Bu bildirimde a int türden, pi int * türünden ve pf ise int (*)(int) türündendir. Şimdi aşağıdaki gibi bir fonksiyon göstericisine uyumlu bir fonksiyonun adresini atayalım: void foo(void) { ... } .... void (*pf)(void); pf = foo; Pekiyi bu pf yoluyla bu fonksiyonu nasıl çağırabiliriz? İşte bunun C'de iki yolu vardır: -> pf(...) sentaksı ile çağrıma -> (*pf)(...) sentaksı ile çağırma her ikisi de eşdeğerdir. Her iki sentaksta da pf göstericisinin içerisindeki adrste bulunan fonksiyon çağrılmaktadır. Aslında genel olarak bir fonksiyon da zaten bu iki sentaksla da çağrılabilmektedir. Örneğin: void foo(void) { ... } ... foo(); (*foo)(); Şimdi de bu konuyla ilgili örneklere bakalım: * Örnek 1, #include void foo(void) { printf("foo\n"); } int main(void) { void (*pf)(void) = foo; pf(); (*pf)(); foo(); (*foo)(); return 0; } * Örnek 2, Aşağıda fonksiyon göstericileri yoluyla fonksiyon çöağrılmasına bir örnek verilmektedir. #include int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int main(void) { int (*pf)(int, int); int result; pf = add; result = pf(10, 20); printf("%d\n", result); pf = mul; result = pf(10, 20); printf("%d\n", result); return 0; } Fonksiyon Göstericileri hakkında şu noktalara da dikkat etmeliyiz: >> Bir fonksiyon göstericisine farklı türdne bir fonksiyonun adresini atayamayız. Eğer atamaya çalışırsak bu durum C'de geçerli olmadığıu için derleme işlemi başarılı olmaz.i (Tabii bazı derleyiciler bu durumda bir uyarı mesajı verip programcıyı affedebilmektedir.) Örneğin: void foo(int a) { ... } ... void (*pf)(int); pf = foo; /* geçersiz' */ Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include void foo(void (*pf)(void)) { pf(); } void bar(void) { printf("bar\n"); } int main(void) { foo(bar); return 0; } >> Fonksiyon göstericilerine bazı işlemlerin yapılabilmesi için gereksinim duyulmaktadır. Örneğin callback fonksiyon mekanizması fonksiyon göstericileri yoluyla sağlanmaktadır. Bir fonksiyon belli bir durum oluştuğunda parametresiyle aldığı fonksiyonu çağırıyorsa bu duruma callback fonksiyon mekanizaması denilmektedir. Örneğin: void for_each(int *pi, size_t size, void (*pf)(int *)) { for (size_t i = 0; i < size; ++i) pf(&pi[i]); } Burada for_each fonksiyonu int bir dizinin tüm elemanlarını dolaşmakta ancak dolaşırken bizim verdiğimiz bir fonksiyonu da çağırmaktadır. Bizim verdiğimiz callback fonksiyona for_each dizinin elemanlarının adreslerini geçirmektedir. Böylece callback fonksiyon dizi elemanlarını değiştirebilecektir. Örneğin: void disp(int *pi) { printf("%d\n", *pi); } void square(int *pi) { *pi = *pi * *pi; } ... int a[] = {10, 20, 30, 40, 50}; for_each(a, 5, disp); for_each(a, 5, square); for_each(a, 5, disp); Görüldüğü gibi fonksiyon göstericileri bir fonksiyonun davranışının dışarıdan değiştirilerek fonksiyonun genelleştirilmesi amacıyla kullanılabilmektedir. Örneğin bir GUI uygulamasında bir düğme GUI elemanına (pushbutton) tıklandığında biz belli bir işlemin yapılmasını isteyebiliriz. Ancak düğmenin kodlarını biz değil ilgili kütüphaneyi yazanlar yazmıştır. İşte düğmenin kodlarını yazan kişiler farfenin onun üzerinde tıklanıp tıklanmadığını kendiileri kontrol etmekte, eğer fare düğme üzerinde tıklanmışsa bizim verdiğimiz bir fonksiyonu çağırmaktadır. * Örnek 1, #include void for_each(int *pi, size_t size, void (*pf)(int *)) { for (size_t i = 0; i < size; ++i) pf(&pi[i]); } void disp(int *pi) { printf("%d\n", *pi); } void square(int *pi) { *pi = *pi * *pi; } int main(void) { int a[] = {10, 20, 30, 40, 50}; for_each(a, 5, disp); for_each(a, 5, square); for_each(a, 5, disp); return 0; } >> Fonksiyon parametrelerinde fonksiyon göstericileri (tıpkı normal göstericilerde olduğu gibi) alternatif sentaksla da belirtilebilmektedir. Örneğin: void foo(void pf(void)) { ... } Bu bildirim tamamne geçerlidir ve aşağıdakiyle tamamen eşdeğerdir: void foo(void (*pf)(void)) { ... } >> Fonksiyon adresleri typedef edilerek daha kolay bir kullanım sağlanabilmektedir. Örneğin: typedef void (*PF)(void); Burada PF geri dönüş değeri void olan ve parametresi void olan bir fonksiyon adresi türüdür. Yani sembolik olarak PF aslında void (*)(void) türünü temsil etmektedir. Örneğin: PF pf; bildirimi ile, void (*pf)(void); bildirimi tamamen yanı anlama gelmeketedir. >> Her elemanı bir fonksiyon göstericisi olan fonksiyon gösterici dizileri de bildirilebilir. Bu bildirimde köşeli parantezler ve * atomu parantez içerisine alınmalıdır. Örneğin: void (*pfs[5])(void); Burada pfs 5 eleanlı bir dizidir. Bu dizinin her elemanı "geri dönüş değeri void olan ve parametresi void olan" bir fonksiyon göstericisidir. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include void foo(void) { printf("foo\n"); } void bar(void) { printf("bar\n"); } void tar(void) { printf("tar\n"); } int main(void) { void (*pfs[3])(void); pfs[0] = foo; pfs[1] = bar; pfs[2] = foo; for (int i = 0; i < 3; ++i) pfs[i](); return 0; } >> Fonksiyon gösterici dizilerine de küme parantezleri içerisinde ilkdeğer verebiliriz. Tabii verdiğimiz ilkdeğerlerin aynı türden fonksiyon adresleri olması gerekir. Örneğin: void (*pfs[3])(void) = {foo, bar, tar}; Tabii yine ilkdeğer verirken dizi uzunluğu belirtilmeyebilir. Örneğin: void (*pfs[])(void) = {foo, bar, tar}; Burada pfs dizisine üç elemanla ilkdeğer verildiği için pfs üç elemanlı bir dizidir. Yukarıda da belirtitğimiz gibi typedef bildirimi ile bu tür tanımlşamaları daha kolay yapabiliriz. Örneğin: typedef void (*PF)(void); ... PF pfs[] = {foo, bar, tar}; Tabii istersek fonksiyon gösterici dizileri için de typedef işlemi yapabiliriz. Örneğin: typedef void (*PFS[3])(void); ... PFS pfs = {foo, bar, tar}; Burada artık PFS üç elemanlı geri dönüş değeri void parametresi void olan fonksiyon göstericisi dizisini temsil etmektedir. Yani PFS türünün sembolik gösterimi void (*[3])(void) biçimindedir. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, #include void foo(void) { printf("foo\n"); } void bar(void) { printf("bar\n"); } void tar(void) { printf("tar\n"); } int main(void) { void (*pfs[3])(void) = {foo, bar, tar}; for (int i = 0; i < 3; ++i) pfs[i](); return 0; } >> Her ne kadar fonksiyon isimleri zaten fonksiyonların adreslerini belirtiyorsa da istenirse fonksiyon isimleri & operatörüyle de kullanılanilir. Yani aslında C'de foo bir fonksiyon belirtmek üzere foo ifadesi ile &foo ifadesi eşdeğerdir. Benzer biçimde foo bir fonksiyon adresi belirtmek üzere bu fonksiyonu çağırmak için foo() ifadesiyle tamamen eşdeğer (*foo)() ifadesi de kullanılabilmektedir. Örneğin: * Örnek 1, #include void foo(void) { printf("foo\n"); } int main(void) { void (*pf)(void); pf = &foo; /* eşdeğeri: pf = foo */ (*pf)(); /* eşdeğer: pf() */ return 0; } >> C'de (ve C++'ta) data adreslerinden fonksiyon adreslerine fonksiyon adreslerinden data adreslerine bir dönüştürme yapılamamaktadır. Yani C standartlarına göre tür dönüştürmesi yapılsa bile bu dönüştürme geçerli değildir. Örneğin: void (*pf)(void); int pi; pi = (int *)pf; /* geçersiz! böyle bir dönüştürme yok! */ Her ne kadar böylesi dönüştürmeler C'de geçerli değilse de Microsoft, gcc ve clang derleyicilerinde bu dönüştürmelere izin verilmektedir. C'de (C++'ta da böyle) void göstericiler data göstericisi olarak kabul edilmektedir. Yani void göstericilere biz türü ne olursa olsun nesnelerin adreslerini atayabiliriz ancak fonksiyon adreslerini atayamayız. Örneğin: int a; double b; void foo(void); void *pv; ... pv = &a; /* geçerli */ pv = &b; /* geçerli */ pv = foo; /* geçersiz! fonksiyon adreslerinden data adreslerine, data adreslerinden fonksiyon adreslerine dönüştürme yok! */ Ancak Microsoft derleyicileri, gcc ve clang derleyicileri bunu kabul etmektedir. Fonksiyon adreslerinin data adreslerine dönüştürülmesi ve data adreslerinin fonksiyon adreslerine dönüştürülmesi aslında dolaylı bir biçimde sağlanabilmektedir. Örneğin elimizde bir fonksiyon adresi olsun biz de bunu void göstericiye atamak isteyelim: void (*pf)(void); void *pv; ... pv = (void *)pf; /* geçersiz! */ Burada pf'nin adresi bir fonksiyon adresi değildir. Fonksiyon göstericisinin adresidir. O halde bu işlem şöyle yapılabilir. pv = *(void **) &pf; Buradaki anahtar nokta bir fonksiyon göstericisinin adresinin bir fonksiyon adresi değil data adresi olduğudur. Bunun tersi de şöyle yapılabilir: void (*pf)(void); void *pv; ... *(void **)&pf = pv; C'de (ve C++'ta) NULL adres fonksiyon göstericilerine de atanabilmektedir. Örneğin: void (*pf)(void) = 0; Burada pf göstericisine int 0 değil, o sistemdeki NULL adres atanmıştır. Tabii C standartlarına göre (ancak C++'ta böyle değil) NULL adres (void *)0 biçiminde de belirtilebilir. Bu özel bir durum olduğu için data adresi kabul edilmemektedir. Örneğin: void (*pf)(void) = (void *)0; Burada her ne kadar sanki void bir adres fonksiyon göstericisine atanıyor gibiyse de "(void *)0" ifadesinin NULL adres biçiminde özel bir anlamı vardır. >> Bir fonksiyon adresi başka türden bir fonksiyon adresine dönüştürülebilir. Örneğin: void foo(int a); ... void (*pf)(int); pf = foo; /* geçeriz! */ pf (void (*)(void))foo /* geçerli */ Tabii bu tür dönüştürmeleri typedef işlemleriyle daha kolay yapabiliriz. Örneğin: typedef void(*PF)(void); void foo(int a); ... void (*pf)(int); pf = foo; /* geçeriz! */ pf (PF)foo /* geçerli */ >> Bir fonksiyonun geri dönüş değeri de bir fonksiyon adresi olabilir. Bu durumda * atomu yine parantez içerisine alınmalı parantezin soluna geri dönüş değerine ilişkin fonksiyonun geri dönüş değerinin türü parantezin sağına ise geri dönüş değerine ilişkin fonksiyonun parametresi yazılmalıdır. Örneğin: void (*foo(int a))(int) { ... } Burada foo fonksiyonunun parametresi int türdendir. Ancak geri dönüş değeri paramtresi int türden olan geri dönüş değeri void türden olan bir fonksiyon adresidir. Buradaki tanımla adım adım şöyle oluşturulmaktadır: -> foo fonksiyonunun parametresi int türdendir. Örneğin, "...foo(int a)..." -> foo fonksiyonunun geri dönüş değeri bir fonksiyon adresidir. Örneğin, "...(*foo(int a))..." -> foo fonksiyonunun geri dönüş değerine ilişkin fonksiyonun geri dönüş değeri void türdendir. Örneğin, "void (*foo(int a))..." -> foo fonksiyonunun geri dönüş değerine ilişkin fonksiyonun parametresi ise int türdendir. Örneğin: "void (*foo(int a))(int)" Aşağıda böylesi bir fonksiyon göstericisinin kullanımına ilişkin örnek verilmiştir: * Örnek 1, #include void bar(void) { printf("bar\n"); } void (*foo(void))(void) { return bar; } int main(void) { void (*pf)(void); pf = foo(); pf(); return 0; } Bu tür bildirimlerde typedef işlemleri bildirimleri oldukça kolaylaştırmaktadır. Örneğin: * Örnek 1, #include typedef void (*PF)(void); void bar(void) { printf("bar\n"); } PF foo(void) { return bar; } int main(void) { PF pf; pf = foo(); pf(); return 0; } >> C'de fonksiyon göstericilerine ilişkin daha karmaşık bildirimler söz konusu olabilmektedir. Ancak neyse ki bu tür bildirimlerle oldukça seyrek karşılaşılmaktadır. Örneğin şçyle bir fonksiyon yazmak isteyelim: -> Fonksiyonumuz ismi foo olsun ve parametresi void türden olsun, -> Fonksiyonumuzun geri dönüş değeri bir fonksiyon adresi olsun ve o fonksiyon adresinin parametresi int türden olsun. -> Fonksiyonumuzun geri dönüş değerine ilişkin fonksiyonun geri dönüş değeri de parametresi void geri dönüş değeri void olan bir fonksiyon adresi olsun. Şimdi yuakrıdaki foo fonksiyonunu tek bir cümleyle ifade etmeye çalışalım: "foo parametresi void olan geri dönüş değeri parametresi int olan geri dönüş değeri paramtresi void olan geri dönüş değeri void olan bir fonksiyon adresidir." Şimdi bu fonksiyonu adım adım yazmaya çalışalım: -> foo fonksiyonun kendi parametresi void "...foo(void)..." -> foo fonksiyonunun geri dönüş değeri bir fonksiyon adresi "...(*foo(void))..." -> foo fonksiyonunun geri dönüş değerine ilişkin fonksiyonun parametresi int "...(*foo(void))(int)..." -> foo fonksiyonun geri dönüş değerine ilişkin fonksiyon adresinin geri dönüş değeri de fonksiyon adresi "...(*(*foo(void))(int))..." -> foo fonksiyonun geri dönüş değerine ilişkin fonksiyon adresinin geri dönüş değerine ilişkin fonksiyonun parametresi void "...(*(*foo(void))(int))(void)" -> foo fonksiyonun geri dönüş değerine ilişkin fonksiyon adresinin geri dönüş değerine ilişkin geri dönüş değeri void "void (*(*foo(void))(int))(void)" İş bu foo fonksiyonu da şöyle tanımlanabilir: void(*(*foo(void))(int))(void) { ... } Pekiyi burada foo fonksiyonu nasıl bir fonksiyon adresine geri dönmektedir? Tabii parametresi int olan geri dönüş değeri parametresi void olan geri dönüş değeri void olan bir fonksiyon adresine. Örneğin: void (*bar(int a))(void) { ... } void(*(*foo(void))(int))(void) { return bar; } Pekiyi bar fonksiyonun neye geri dönmesi gerekmektedir? Tabii geri dönüş değeri void olan, parametresi void olan bir fonksiyonun adresine. * Örnek 1, #include void tar(void) { printf("tar\n"); } void (*bar(int a))(void) { return tar; } void(*(*foo(void))(int))(void) { return bar; } int main(void) { void (*(*pf1)(int))(void); void (*pf2)(void); pf1 = foo(); pf2 = pf1(0); pf2(); foo()(0)(); return 0; } Tabii typedef işlemi ile yukarıdaki karmaşık bildirimler daha basit biçimde de ifade edilebilirdi. * Örnek 1, #include typedef void (*PF1)(void); typedef PF1(*PF2)(int); void tar(void) { printf("tar\n"); } PF1 bar(int a) { return tar; } PF2 foo(void) { return bar; } int main(void) { PF2 pf2; PF1 pf1; pf2 = foo(); pf1 = pf2(0); pf1(); return 0; } Bu tür bildirimler daha karmaşık hale de getirilebilir. Ancak uygulamada bu kadar karmaşık bildirimlerle karşılaşılmamaktadır. Uygulamada en fazla bir fonksiyonun geri dönüş değerinin basit bir fonksiyon adresi olması durumuyla karşılaşılmaktadır. * Örnek 1, #include void tar(void) { printf("tar\n"); } void (*bar(int a))(void) { return tar; } void(*(*foo(void))(int))(void) { return bar; } int main(void) { void (*(*pf1)(int))(void); void (*pf2)(void); pf1 = foo(); pf2 = pf1(0); pf2(); return 0; } * Örnek 2, signal isimli bir POSIX fonksiyonunun prototipi aşağıdaki gibidir: #include void (*signal(int sig, void (*func)(int)))(int); Burada signal fonksiyonu iki parametreli bir fonksiyondur. Fonksiyonun birinci parametresi int türden ikinci parametresi ise bir fonksiyon türündendir. Yani fonksiyonun ikinci parametresi, geri dönüş değeri void olan parametresi int olan bir fonksiyon adresini almaktadır. Burada signal fonksiyonunun geri dönüş değeri void, parametresi int olan bir fonksiyon adresidir. başlık dosyası içerisinde aşağıdaki gibi bir typede file bildirim kolaylaştırılmıştır: typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); * Örnek 3.0, Yukarıda da belirtildiği gibi fonksiyon göstericileri özellikle birtakım işlemleri genelleştirmek amacıyla kullanılmaktadır. Örneğin, Windows için ListDir isimli bir fonksiyon yazmak isteyelim. Fonksiyon bir dizindeki dizin girişlerini bulsun ancak onları yazdırmak yerine bizim ona verdiğimiz bir fonksiyonu çağırsın. Bu sayede biz o fonksiyona istediğimiz bir şeyi yaptırabiliriz. Böyle bir fonksiyonun parametrik yapısı aşağıdaki gibi olabilir: BOOL ListDir(const char *szPath, BOOL (*Proc)(WIN32_FIND_DATA *)); Fonksiyonun birinci parametresi joker karakterleri de içeren dizine ilişkin bir yol ifadesidir. İkinci parametre ilgili dizinde bir dizin girişi bulunduğunda onun bilgilerinin bulunduğu WIN32_FIND_DATA yapısının adresi ile çağrılacak olan fonksiyonu belirtmektedir. Yani ListDir fonksiyonu her dizin girişini buldukça o dizin girişi ile ikinci paramtresinde belirtilen bizim fonksiyonumuzu çağırmaktadır. Tabii bu tür durumlarda callback fonksiyon yoluyla işlemin durdurulması da gerekebilmektedir. Bu nedenle ikinci parametedeki fonksiyon BOOL bir değere geri döndürülmüştür. Bu fonksiyon sıfır dışı bir değerleile geri döndürülürse işlemler devam edecek 0 ile geri döndürülürse dolaşım durdurulacaktır. Pekiyi burada ListDir fonksiyonunun geri dönüş değeri neyi belirtmektedir? İşte fonksiyonun geri dönüş değeri işlemin başarısını belirtebilir. Aşağıdabu fonksiyonun yazımına ve kullanımına ilişkin bir örnek verilmiştir: #include #include #include #include BOOL ListDir(const char *szPath, BOOL(*Proc)(WIN32_FIND_DATA *)); void ExitSys(LPCSTR lpszMsg); BOOL FindFile(WIN32_FIND_DATA *fd) { printf("%s\n", fd->cFileName); if (!_strcmpi(fd->cFileName, "notepad.exe")) { printf("notepad.exe found\n"); return FALSE; } return TRUE; } BOOL DispFile(WIN32_FIND_DATA *fd) { unsigned long long ullSize; SYSTEMTIME sysTime; FileTimeToLocalFileTime(&fd->ftLastWriteTime, &fd->ftLastWriteTime); FileTimeToSystemTime(&fd->ftLastWriteTime, &sysTime); printf("%02d.%02d.%04d %02d:%02d ", sysTime.wDay, sysTime.wMonth, sysTime.wYear, sysTime.wHour, sysTime.wMinute); if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) printf("%-14s", ""); else { ullSize = (unsigned long long)fd->nFileSizeHigh << 32 | fd->nFileSizeLow; printf("%14llu", ullSize); } printf(" %s\n", fd->cFileName); return TRUE; } int main(void) { if (!ListDir("c:\\windows\\*.*", FindFile)) ExitSys("ListDir"); printf("----------------------------\n"); if (!ListDir("c:\\windows\\*.*", DispFile)) ExitSys("ListDir"); return 0; } BOOL ListDir(const char *szPath, BOOL(*Proc)(WIN32_FIND_DATA *)) { HANDLE hFileFind; WIN32_FIND_DATA findData; BOOL bResult = TRUE;; if ((hFileFind = FindFirstFile(szPath, &findData)) == INVALID_HANDLE_VALUE) return FALSE; do { if (!Proc(&findData)) goto EXIT; } while (FindNextFile(hFileFind, &findData)); if (GetLastError() != ERROR_NO_MORE_FILES) return FALSE; EXIT: FindClose(hFileFind); return TRUE; } void ExitSys(LPCSTR lpszMsg) { DWORD dwLastErr = GetLastError(); LPTSTR lpszErr; if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpszErr, 0, NULL)) { fprintf(stderr, "%s: %s", lpszMsg, lpszErr); LocalFree(lpszErr); } exit(EXIT_FAILURE); } * Örnek 3.1, Yukarıdaki işlemin UNIX/Linux eşdeğeri de aşağıdaki gibi yazılabilir: #include #include #include #include #include #include int list_dir(const char *path, int (*proc)(const struct stat *, const char *name)); void exit_sys(const char *msg); int disp_file(const struct stat *finfo, const char *name) { printf("%-30s%jd\n", name, (intmax_t)finfo->st_size); return 1; } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if (list_dir(argv[1], disp_file) == -1) exit_sys("list_dir"); return 0; } int list_dir(const char *path, int (*proc)(const struct stat *, const char *name)) { DIR *dir; struct dirent *de; char fpath[PATH_MAX]; struct stat finfo; int status = 0; if ((dir = opendir(path)) == NULL) return -1; while ((errno = 0, de = readdir(dir)) != NULL) { snprintf(fpath, PATH_MAX, "%s/%s", path, de->d_name); if (stat(fpath, &finfo) == -1) { status = -1; goto EXIT; } if (!proc(&finfo, de->d_name)) goto EXIT; } if (errno) status = -1; EXIT: closedir(dir); return status; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 4.0, C'de her türden diziyi sıraya dizebilen bir fonksiyon yazabilir miyiz? Yazarsak bunu nasıl yazabiliriz? Örneğin aşağıdaki gibi bir fonksiyon yalnızca int türden dizileri sıraya dizebilir: void sort(int *pi, size_t size); Her tür için bu fonksiyonu içi aynı olacak biçimde yeniden yazarsak belki bir ölçüde hedefimize ulaşabiliriz. Örneğin: void sort_int(int *pi, size_t size); void sort_long(long *pi, size_t size); void sort_double(double *pi, size_t size); ... Ancak burada yine bir problem vardır. Biz kendi yapımızı sıraya dizecek olsak yapımıza uygun yeni bir fonksiyon yazmamız gerekir. Türden bağımsız dizi işlemleri programlama dilelrinde önemli bir sorundur. C++ bunu sağlamak için ismine "şablon (template)" denilen bir mekanizma oluşturmuştur. Önce C# sonra da Java C++'ın şablon mekanizmasından esinlenerek benzer bir mekanizmayı "generic" adı altında oluşturmuştur. Yeni tasarlanan dilelrin çoğunda (Swift gibi, Kotlin gibi Rust gibi) bu generic mekanizma artık bulundurulmaktadır. Ancak C'de böyle bir mekanizma yoktur. Python gibi Ruby gibi dinamik tür sistemine sahip programlama dillerinde zaten bu generic mekanizma doğuştan vardır. Sort işlemi hangi alagoritmaya göre yapılırsa yapılsın dizinin iki elemanın karşılaştırılması ve duruma göre yer değiştirilmesi işlemlerini uygulamaktadır. Yani biz quick sort algoritmasını uygulasak da boubble sort algoritmasını uygulasak da eninde sonunda dizinin iki elemanını karşılaştırıp duruma göre onları yer değiştiririz. Türden bağımsız bir sort fonksiyonu yazılacaksa fonksiyon sıraya dizeceği dizinin başlangıç adresini void gösterici biçiminde almalıdır. Bu fonksiyon bu dizinin türünü bilmediğine göre iki elemanı karşılaştıramaz. Ancak dizinin türünü onu çağıran programcı bilmektedir. O zaman fonksiyon dizinin iki elemanını karşılaştırma işlemini onu çağıran programcıya bırakabilir. Tabii bunu bir fonksiyon gösterici kullanarak sağlayacaktır. Fonksiyonun dizinin iki elemanını yer değiştirebilmesi için dizinin elemanlarının kaç byte uzunlukta olduğunu biliyor olması gerekir. O zaman böyle bir fonksiyon dizinin bir elemanın byte uzunluğunu da parametre olarak almalıdır. Tabii fonksiyonun dizinin kaç eleman uzunluğunda olduğunu da biliyor olması gerekir. Bu durumda böyle bir fonksiyonun aşağidaki parametrik yapıya sahip olması uygun olacaktır: void sort(void *pv, size_t count, size_t width, int (*compare)(const void *, const void *)) Fonksiyonun birinci parametresi dizinin başlangıç adresini belirtir. İkinci parametre dizinin eleman sayısını belirtmektedir. Üçüncü parametre dizinin bir elemanının byte uzunluğunu belirtir. Son parametre ise karşılaştırma fonksiyonun adresini almaktadır. Fonksiyon ne zaman bir karşılaştırma yapılacak olsa dizinin karşılaştırılacak iki elemanının adreslerini bu fonksiyona geçirir ve bu fonksiyonu çağırır. Karşılaştırma fonksiyonunu yazan programcı "birinci parametresiyle belirtilen dizi elemanı ikinci parametresiyle belirtilen dizi elemanından büyükse fonksiyonu pozitif herhangi bir değerle, ikinci parametresiyle belirtilen dizi elemanı birinci parametresiyle belirtilen dizi elemanındna büyükse negatif herhangi bir değerle ve bu iki eleman eşitse sıfır değeri ile" geri döndürmelidir. Örneğin böyle bir fonksiyonla int bir dizi şöyel sıraya dizilebilir: int cmp(const void *ptr1, const void *ptr2); int main(void) { int a[10] = {3, 6, 2, 12, 1, 87, 45, 23, 54, 78}; sort(a, 10, sizeof(int), cmp); for (int i = 0; i < 10; ++i) printf("%d ", a[i]); printf("\n"); return 0; } int cmp(const void *ptr1, const void *ptr2) { const int *elem1 = (const int *)ptr1; const int *elem2 = (const int *)ptr2; if (*elem1 > *elem2) return 1; if (*elem1 < *elem2) return -1; return 0; } Aşağıda boubble sort algoritması ile türden bağımsız sort işlemi yapan bir örnek verilmiştir. #include #include void bsort(void *pv, size_t count, size_t width, int (*compare)(const void *, const void *)); int cmp(const void *ptr1, const void *ptr2); int main(void) { int a[10] = {3, 6, 2, 12, 1, 87, 45, 23, 54, 78}; bsort(a, 10, sizeof(int), cmp); for (int i = 0; i < 10; ++i) printf("%d ", a[i]); printf("\n"); return 0; } void bsort(void *pv, size_t count, size_t width, int (*compare)(const void *, const void *)) { unsigned char *pc = (unsigned char *)pv; unsigned char *elem1, *elem2; unsigned char temp; for (size_t i = 0; i < count - 1; ++i) for (size_t k = 0; k < count - 1 - i; ++k) { elem1 = pc + k * width; elem2 = pc + (k + 1) * width; if (compare(elem1, elem2) > 0) for (size_t j = 0; j < width; ++j) { temp = elem1[j]; elem1[j] = elem2[j]; elem2[j] = temp; } } } int cmp(const void *ptr1, const void *ptr2) { const int *elem1 = (const int *)ptr1; const int *elem2 = (const int *)ptr2; if (*elem1 > *elem2) return 1; if (*elem1 < *elem2) return -1; return 0; } * Örnek 4.1, Aslında yukarıda yazdığımız bsort fonksiyonu ile aynı parametrik yapıya sahip qsort isimli bir standart C fonksiyonu zaten bulunmaktadır. void qsort(void *base, size_t nmemb, size_t size, int (*compare)(const void *, const void *)); Fonksiyonun yine birinci parameresi dizinin başlangıç adresini, ikinci parametresi onun eleman sayısını, üçüncü parametresi bir elemanının byte uzunluğunu, dördüncü parametresi ise karşılaştımra fonksiyonunu belirtmektedir. Bu karşılaştırma fonksiyonu programcı tarafından eğer soldaki eleman sağdaki elemandan büyükse pozitif herhangi bir değere, eğer sağdaki eleman soldaki elemandan büyükse negatif herhangi bir değere ve iki eleman biribine eşitse 0 değerine geri dönmelidir. C standartlarında fonksiyonun hangi algoritmayı kullanması gerektiği belirtilmemiş olsa da tipik olarak fonksiyon mevcut C derleyicilerde "quick sort" algoritmasını kullanarak gerçekleştirilmektedir. #include #include #include struct PERSON { char name[64]; int no; }; int cmp_no(const void *ptr1, const void *ptr2); int cmp_name(const void *ptr1, const void *ptr2); int main(void) { struct PERSON persons[10] = { {"Kazim Tanis", 654}, {"Hasan Aslan", 145}, {"Kamil Zorlu", 487}, {"Necati Ergin", 534}, {"Ali Serce", 931}, {"Guray Sonmez", 245}, {"Fehmi Oz", 543}, {"Ayse Er", 321}, {"Sibel Cetinsoy", 423}, {"Mualla Yilmaz", 739} }; qsort(persons, 10, sizeof(struct PERSON), cmp_no); for (size_t i = 0; i < 10; ++i) printf("%-20s%d\n", persons[i].name, persons[i].no); printf("-------------------------------------------\n"); qsort(persons, 10, sizeof(struct PERSON), cmp_name); for (size_t i = 0; i < 10; ++i) printf("%-20s%d\n", persons[i].name, persons[i].no); return 0; } int cmp_no(const void *ptr1, const void *ptr2) { const struct PERSON *per1 = (const struct PERSON *)ptr1; const struct PERSON *per2 = (const struct PERSON *)ptr2; if (per1->no > per2->no) return 1; if (per1->no < per2->no) return -1; return 0; } int cmp_name(const void *ptr1, const void *ptr2) { const struct PERSON *per1 = (const struct PERSON *)ptr1; const struct PERSON *per2 = (const struct PERSON *)ptr2; return strcmp(per1->name, per2->name); } Biz bu örnekte küçükten büyüğe sıralama yaptık. eğer büyükten küçüğe sıralama yapılmak istenirse bu durumda karşılaştırma fonksiyonu ters yazılmalıdır. Yani birinci parametre ikinci parametreden büyükse fonksiyon negatif herhangi bir değere, küçükse pozitif herhangi bir değere ve eşitse sıfır değerine geri döndürülmelidir. Tabii karşılatırma fonksiyonu uzunsa ya da bizim tarafımızdan yazılmadıysa karşılatırma fonksiyonunu sarmalayan bir fonksiyon yazıp onun negatifi ile geri dönülebilir. * Örnek 4.2.0, Bir gösterici dizisini qsort fonksiyonuyla sıraya dizerken aslında göstericilerin adresleriyle karşılaştırma fonksiyonu çağrılmaktadır. Aşağıdaki örnekte isimlerden oluşan const char * türünden elemanlara sahip olan bir gösterici dizisi sıraya dizilmiştir. Burada aslında sıraya dizilen gösterici dizisindeki adreslerdir. #include #include #include struct PERSON { char name[64]; int no; }; int cmp(const void *ptr1, const void *ptr2); int main(void) { const char *names[10] = { "Kazim Tanis", "Hasan Aslan", "Kamil Zorlu", "Necati Ergin", "Ali Serce", "Guray Sonmez", "Fehmi Oz", "Ayse Er", "Sibel Cetinsoy", "Mualla Yilmaz" }; qsort(names, 10, sizeof(const char *), cmp); for (size_t i = 0; i < 10; ++i) printf("%s\n", names[i]); return 0; } int cmp(const void *ptr1, const void *ptr2) { const char **elem1 = (const char **)ptr1; const char **elem2 = (const char **)ptr2; return strcmp(*elem1, *elem2); } * Örnek 4.2.1, UNIX/Linux sistemlerinde ls -l işleminden elde edilen dosyaların isme göre sıraya dizilerek yazdırılması: #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_PATH 4096 #define BLOCK_SIZE 32 struct lsinfo { struct stat finfo; char *name; }; void exit_sys(const char *msg); const char *get_ls(const struct lsinfo *lsinfo, int hlink_digit, int uname_digit, int gname_digit, int size_digit); int cmp_name(const void *lsinfo1, const void *lsinfo2); int main(int argc, char *argv[]) { DIR *dir; struct dirent *dent; struct lsinfo *lsinfo; int count; char path[MAX_PATH]; struct passwd *pass; struct group *gr; int len; int hlink_digit, uname_digit, gname_digit, size_digit; int i; if (argc != 2) { fprintf(stderr, "wrong number of arguments!..\n"); exit(EXIT_FAILURE); } if ((dir = opendir(argv[1])) == NULL) exit_sys("open"); lsinfo = NULL; for (count = 0, errno = 0; (dent = readdir(dir)) != NULL; ++count) { sprintf(path, "%s/%s", argv[1], dent->d_name); if (count % BLOCK_SIZE == 0) if ((lsinfo = realloc(lsinfo, (count + BLOCK_SIZE) * sizeof(struct lsinfo))) == NULL) { fprintf(stderr, "cannot allocate memory!..\n"); exit(EXIT_FAILURE); } if ((lsinfo[count].name = (char *)malloc(strlen(dent->d_name) + 1)) == NULL) { fprintf(stderr, "cannot allocate memory!...\n"); exit(EXIT_FAILURE); } strcpy(lsinfo[count].name, dent->d_name); if (stat(path, &lsinfo[count].finfo) == -1) exit_sys("stat"); } if (errno != 0) exit_sys("readdir"); closedir(dir); hlink_digit = uname_digit = gname_digit = size_digit = 0; for (i = 0; i < count; ++i) { len = (int)log10(lsinfo[i].finfo.st_nlink) + 1; if (len > hlink_digit) hlink_digit = len; if ((pass = getpwuid(lsinfo[i].finfo.st_uid)) == NULL) exit_sys("getppuid"); len = (int)strlen(pass->pw_name); if (len > uname_digit) uname_digit = len; if ((gr = getgrgid(lsinfo[i].finfo.st_gid)) == NULL) exit_sys("getgrgid"); len = (int)strlen(gr->gr_name); if (len > gname_digit) gname_digit = len; len = (int)log10(lsinfo[i].finfo.st_size) + 1; if (len > size_digit) size_digit = len; } qsort(lsinfo, count, sizeof(struct lsinfo), cmp_name); for (i = 0; i < count; ++i) printf("%s\n", get_ls(&lsinfo[i], hlink_digit, uname_digit, gname_digit, size_digit)); for (i = 0; i < count; ++i) free(lsinfo[i].name); free(lsinfo); return 0; } const char *get_ls(const struct lsinfo *lsinfo, int hlink_digit, int uname_digit, int gname_digit, int size_digit) { static char buf[4096]; static mode_t modes[] = { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH }; struct passwd *pass; struct group *gr; int index = 0; int i; if (S_ISREG(lsinfo->finfo.st_mode)) buf[index] = '-'; else if (S_ISDIR(lsinfo->finfo.st_mode)) buf[index] = 'd'; else if (S_ISCHR(lsinfo->finfo.st_mode)) buf[index] = 'c'; else if (S_ISBLK(lsinfo->finfo.st_mode)) buf[index] = 'b'; else if (S_ISFIFO(lsinfo->finfo.st_mode)) buf[index] = 'p'; else if (S_ISLNK(lsinfo->finfo.st_mode)) buf[index] = 'l'; else if (S_ISSOCK(lsinfo->finfo.st_mode)) buf[index] = 's'; ++index; for (i = 0; i < 9; ++i) buf[index++] = (lsinfo->finfo.st_mode & modes[i]) ? "rwx"[i % 3] : '-'; buf[index] = '\0'; index += sprintf(buf + index, " %*llu", hlink_digit, (unsigned long long)lsinfo->finfo.st_nlink); if ((pass = getpwuid(lsinfo->finfo.st_uid)) == NULL) return NULL; index += sprintf(buf + index, " %-*s", uname_digit, pass->pw_name); if ((gr = getgrgid(lsinfo->finfo.st_gid)) == NULL) return NULL; index += sprintf(buf + index, " %-*s", gname_digit, gr->gr_name); index += sprintf(buf + index, " %*lld", size_digit, (long long)lsinfo->finfo.st_size); index += strftime(buf + index, 100, " %b %e %H:%M", localtime(&lsinfo->finfo.st_mtime)); sprintf(buf + index, " %s", lsinfo->name); return buf; } int istrcmp(const char *s1, const char *s2) { while (tolower(*s1) == tolower(*s2)) { if (*s1 == '\0') return 0; ++s1; ++s2; } return tolower(*s1) - tolower(*s2); } int cmp_name(const void *pv1, const void *pv2) { const struct lsinfo *lsinfo1 = (const struct lsinfo *)pv1; const struct lsinfo *lsinfo2 = (const struct lsinfo *)pv2; return istrcmp(lsinfo1->name, lsinfo2->name); } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5.0, Fonksiyon göstericilerine iyi bir alıştırma için scandir fonksiyonu kullanılabilir. scandir bir dizindeki struct dirent bilgilerini elde eden bir POSIX fonksiyonudur. Fonksiyonun tasarımı biraz karışık biçimdedir. Bu karışıklık fonksiyonun rahat kullanılmasını engellemektedir. Fonksiyonun prototipi şöyledir: #include int scandir(const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **) ); Fonksiyonun birinci parametresi dizin listesi elde edilecek dizin'in yol ifadesini almaktadır. Fonksiyon o dizindeki girişlerin struct dirent bilgilerini malloc ile tahsis ettiği dinamik alana kopyalar ve alanların adreslerini de bir gösterici dizisine yerleştirir. Gösterici dizisinin adresine de bizim ikinci parametreyle verdiğimiz göstericiyi gösteren göstericinin içerisine yerleştirmektedir. Tabii bu gösterici dizisi de dinamik biçimde tahsis edilmiştir. Dolayısıyla bunların free hale getirilmesi programcının sorumluluğundadır. Fonksiyonun üçüncü parametresi filtreleme yapmak için kullanılan callback fonksiyonun adresini almaktadır. scandir her bulduğu giriş için bu fonksiyonu çağırır. Eğer bu fonksiyon sıfır dışı bir değerle geri dönerse o girişi listeye dahil eder. Eğer bu callback fonksiyon sıfır ile geri dönerse o girişi listeye dahil etmez. scandir fonksiyonunun son parametresi girişlerin sıraya dizilmesi için kullanılan karşılaştırma fonksiyonu almaktadır. Gerçi dirent yapısı zaten inode ve giriş isminde oluştuğu için anlamlı olan sıraya dizme giriş ismine göre olacaktır. Giriş ismine göre sıraya dizme işlemi için alpasort isimli hazır bir karşılaştımr fonksiyonu da bulundurulmuştur. Aslında fonksiyonun son iki parametresine NULL adres de geçilebilmektedir. scandir fonksiyonu çaşarı durumunda diziye yerleştirilen giriş sayısı ile başarısızlık durumunda -1 değeri ile geri dönmektedir. Aşağıda scandir fonksiyonunun kullanımına bir örnek verilmiştir. #include #include #include #include #include void exit_sys(const char *msg); int mycallback(const struct dirent *de) { struct stat finfo; char path[4096]; sprintf(path, "/usr/include/%s", de->d_name); if (stat(path, &finfo) == -1) exit_sys("stat"); return finfo.st_size < 1000; } int main(void) { struct dirent **dents; int i, count; if ((count = scandir("/usr/include", &dents, mycallback, NULL)) == -1) exit_sys("scandir"); for (i = 0; i < count; ++i) printf("%s\n", dents[i]->d_name); for (i = 0; i < count; ++i) free(dents[i]); free(dents); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 5.1, #include #include #include #include void exit_sys(const char *msg); int mycallback(const struct dirent *de) { if (tolower(de->d_name[0]) == 'a') return 1; return 0; } int main(void) { struct dirent **dents; int i, count; if ((count = scandir("/usr/include", &dents, mycallback, NULL)) == -1) exit_sys("scandir"); for (i = 0; i < count; ++i) printf("%s\n", dents[i]->d_name); for (i = 0; i < count; ++i) free(dents[i]); free(dents); return 0; } void exit_sys(const char *msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 6, Komut satırı (REPL) oluşturan bir örnek. Bu örnekte komut bulunduğunda belirlenen bir fonksiyon çağrılmaktadır. /* myshell.c */ #include #include #include #include #include #define PROMPT "CSD>" #define MAX_CMD_LINE 4096 #define MAX_CMD_PARAMS 128 #define BUFFER_SIZE 8192 void parse_cmdline(void); void cat_cmd(void); void cp_cmd(void); void rename_proc(void); typedef struct tagCMD { char *cmd_name; void (*cmd_proc)(void); } CMD; char g_cmdline[MAX_CMD_LINE]; char *g_params[MAX_CMD_PARAMS]; int g_nparams; CMD g_cmds[] = { {"cat", cat_cmd}, {"cp", cp_cmd}, {"rename", rename_proc}, {NULL, NULL} }; int main(void) { char *str; int i; for (;;) { printf("%s", PROMPT); if (fgets(g_cmdline, MAX_CMD_LINE, stdin) == NULL) continue; if ((str = strchr(g_cmdline, '\n')) != NULL) *str = '\0'; parse_cmdline(); if (g_nparams == 0) continue; if (!strcmp(g_params[0], "exit")) break; for (i = 0; g_cmds[i].cmd_name != NULL; ++i) if (!strcmp(g_cmds[i].cmd_name, g_params[0])) { g_cmds[i].cmd_proc(); break; } if (g_cmds[i].cmd_name == NULL) { printf("invalid command: %s\n", g_params[0]); } } return 0; } void parse_cmdline(void) { char *str; g_nparams = 0; for (str = strtok(g_cmdline, " \t"); str != NULL; str = strtok(NULL, " \t")) g_params[g_nparams++] = str; } void cat_cmd(void) { FILE *f; int ch; if (g_nparams != 2) { printf("cat command missing file!..\n"); return; } if ((f = fopen(g_params[1], "r")) == NULL) { printf("file not found or cannot open file: %s\n", g_params[1]); return; } while ((ch = fgetc(f)) != EOF) putchar(ch); if (ferror(f)) printf("cannot read file: %s\n", g_params[1]); fclose(f); } void cp_cmd(void) { int fds, fdd; char buf[BUFFER_SIZE]; ssize_t result; if (g_nparams != 3) { printf("source and destination path must be specified!..\n"); return; } if ((fds = open(g_params[1], O_RDONLY)) == -1) { printf("file not found or cannot open: %s\n", g_params[1]); return; } if ((fdd = open(g_params[2], O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { printf("file not found or cannot open: %s\n", g_params[2]); goto EXIT2; } while ((result = read(fds, buf, BUFFER_SIZE)) > 0) if (write(fdd, buf, result) != result) { printf("cannot write file: %s\n", g_params[2]); goto EXIT1; } if (result == -1) { printf("cannot read file: %s\n", g_params[1]); goto EXIT1; } printf("1 file copied...\n"); EXIT1: close(fdd); EXIT2: close(fds); } void rename_proc(void) { printf("rename command...\n"); } >> Bir gösterici dizisinin ismi onun ilk elemanının adresi olacağına göre göstericiyi gösteren göstericiye atanmalıdır. * Örnek 1, int x = 10, y = 20, z = 30; int *a[] = {&x, &y, &z}; int **ppi; ppi = a; /* geçerli */ * Örnek 2, #include #include int main(void) { const char *names[] = {"ali", "veli", "selami", "ayse", "fatma", NULL}; const char **pps; pps = names; for (int i = 0; pps[i] != NULL; ++i) puts(pps[i]); return 0; } >> C'de parametre parantezi içerisindeki dizisel gösterim tamamen "gösterici" anlamına gelmektedir. Köşeli parantezlerin içerisindeki sayıların da hiçbir önemi yoktur. Dolayısıyla aşağıdaki prototiplerin hepsi aynıdır: void foo(int *a); void foo(int a[]); void foo(int a[100]); void foo(int a[5]); Bunların hepsi void foo(int *) anlamına gelmektedir. >> Fonksiyonun parametresi göstericiyi gösteren gösterici ise o parametre yine dizi sentaksıyla belirtilebilir. Dolayısıyla aşağıdaki prototipler de yine eşdeğerdir: void bar(int **a); void bar(int *a[10]); void bar(int *a[]); Bunların hepsi void bar(int **) anlamına gelmektedir. Bu durumda örneğin aslında main fonksiyonunun ikinci parametresi de şöyle belirtilebilir: int main(int argc, char **argv) { ... } Ancak geleneksel olarak programcılar bu ikinci parametreyi aşağıdaki gibi belirtmektedir: int main(int argc, char *argv[]) { ... } Ancak bir fonksiyonun parametresi sanki çok boyutlu diziymiş gibi belirtilirse bu tamamen başka bir anlama gelemketdir. Örneğin: void foo(int a[][3]) { ... } Bu bildirmin ne anlamı geldiği izleyen paragraflarda açıklanacaktır. Aşağıda bu kullanıma ilişkin bir örnek verilmiştir: * Örnek 1, #include void foo(int *a[], size_t size) { for (size_t i = 0; i < size; ++i) printf("%d\n", *a[i]); } int main(void) { int x = 10, y = 20, z = 30; int *a[] = {&x, &y, &z}; foo(a, 3); return 0; } >> Fonksiyon parametresi fonksiyon göstericisi ise o da alternatif biçimde fonksiyon sentaksıyla belirtilebilmektedir. Aşağıdaki iki prototip eşdeğerdir. Örneğin: void foo(int (*a)(double)); void foo(int a(double)); Tabii prototipte değişken ismi belirtilmek zorunda olmadığına göre aşağıdaki prototip de yukarıdakilerle eşdeğerdir: void foo(int (double));