16 Aralık 2013 Pazartesi

Function Overloading C++

    Merhaba arkadaşlar bugün yazımızda C++'da function overloading'i işleyeceğiz ki function overloading nesne yönelimli dil özelliği olduğundan diğer dillerde de rahatça kullanabileceksiniz. Function overloading türkçe olarak kitaplar "Fonksyion aşırı yükleme" olarak geçer daha doğru bir tabir ile aynı isimde farklı fonksiyonları kullanma olarak düşünebiliriz.
     Biliyorsunuz ki C 'de bir aynı işlemi int double vb. farklı türler için yapan fonksiyonların isimleri birbirlerinden farklı olmak zorundadır. Bu engel C++ ve nesne yönelimli programlama dillerinde function overloading sayesinde kalkmış bulunmakta.
    Şimdi hangi durumlarda function overloading olur bir ona bakalım;
- Fonksiyonlar aynı scope 'a dahil olmak zorundalar.
- İsimleri aynı, imzaları (signature) farklı olmak zorunda. (imzanın ne olduğunu açıklayacağım).

    Bu temel gerekliliklere yakından bir bakalım. Aynı scope'da olmalı dedik eğer farklı scope'da iseler scope arama sırasına göre ilk bulunan fonksyion çağırılır. Eğer bu fonksiyon ile isim uyumlu parametreler uyumsuz ise error olur.
    İmzaları (signature) farklı dedik peki bu imza nedir. İmza bir fonksiyonun parametre değişkenleri denebilir, geri dönüş değeri imzaya dahil değildir yani int foo(int) ile boolean foo(int) aynı scope da olsalar dahi overload olmazlar error olur.
     
    Anahtar Not : typedef isimleri overload'u etkilemez.

    Anahtar Not : Fonksiyonun anlamını değiştirmeyen const 'lar overload olmazlar fakat fonksiyonun çalışmasını değiştiren const 'lar overload olurlar bunada const overloading denir. ÖR : 
int foo(const int)     int foo(int)  call by value olduğundan anlam değişmez overload olmaz redeclaration olur.
int foo(const int*)   int foo(int *)   call by ref.  olduğundan anlam değişir overload.

    Overload 'u anladığımıza inanıyorum ve Overload Resolution'a (işleme) bölümüne geçiyorum bakalım derleyici overloadlarda hangisini seçeceğine nasıl karar veriyor.

!Fonksiyon çağrısının errorle sonuçlanma sebepleri : 
1) Çağıracak fonksiyon bulamamak uygun parametreli fonksiyonun bulunmaması.
2) Ambiguity durumu (birden fazla uygunluk).

Aşamalar : 

1) İsimden kaç tane var araştırılır. (Aday fonksiyonlar, isimleri ve scopeları aynı olacak, parametreye bu aşamada bakılmaz)

2) Gönderilen argümanlar 1. aşamayı geçen fonksiyonların hangileri ile legal olarak çağırılabilir bakılır. (Match Function)

3) Fonksiyonun seçilme aşaması (Best Match)
Bu aşamada seçilme şu sıra ile yapılır:

Exact Match(Tam Uyumluluk) : Bunun olması için gönderilen fonksiyonun parametre türleri birebir fonksiyon ile tutmalı.

Promotion (Yükseltme) : Alt tür gönderilip fonksiyonda üst türe dönüştürme.

Standart Conversion : Dönüşüm var ise promotion değil!

User-Defined Conversion: Eğer uygun constractor var ise olan dönüşüm.

Bunlar sırası ile kuvvete sahiptir ilk tam uyumluluk durumuna bakılır eğer o var ise diğerleri seçilmez.

Ambiguity : Eğer iki adet aynı klasmanda fonksyion var ise ( iki adet standart conversion gibi) derleyici hangisini seçeceiğini bilemez ve ambiguity olur.

Bugünde yazımızın sonuna geldik zor bir konuyu atlattık düzenlemeleri yapacağım herkese iyi günler hatam var ise affola.

10 Aralık 2013 Salı

C 'de Handle Sistemleri

    Merhaba arkadaşlar bugünkü yazımızda handle sistemlerini sizlere anlatmaya çalışacağım. Handle sistemleri aslında görünüşte nesne yönelimli programlamayı andırmaktadır. Tabiki sınıf anlamında bahsetmekteyim herhangi kalıtım, polimorfizim gibi özellikler tabi ki C 'de mevcut değil. Sadece nesne yönelimli programlamaya benzetmeye çalışacağız.
    Gelelim handle nedir? Handle bir veri yapısına erişmek için kullanılan anahtarlara benzetilebilir. Bir yapı(struct) yada sadece bir tamsayı olabilir. Örnek olarak bir tamsayı olduğunu ve dizi indexini tuttuğunu varsayalım dizide de bizim bilgilerimiz olursa fonksiyonlar aracılığı ile müşteriye(client)'a çesitli bilgiler verebiliriz.
    Handle sistemleri 3 grupta incelenir bunlar;

 1- Handle Başlatan Fonksiyonlar : Bunlar çağıran her kullanıcı için bir adet handle oluşturur ve yapılandırırlar. Nesne yönelimlideki constructor(yapılandırıcı fonksiyon) gibi davranır ve fonksiyon handle değeri ile geri döner. Bunlar genelde create___  veya open___ şeklindedirler.

ÖR :
FILE *fopen(const char *, const char *);


2- Handle Sistemini Kullanan Fonksiyonlar : Bunlar handle'ı alarak müşteriye çeşitli atamalar ve işlemler yapmak için oluşturulurlar. Handle kullanan fonksiyonların bir parametresi Handle'ın kendisidir. Bunlar genelde set ve get fonsiyonlarıdır.

ÖR :
int fputc(int, FILE *);

3- Handle 'ı Sonlandıran Fonksiyonlar : Bu fonksiyonlar handle ile işi biten müşterinin handle'ını yok etmek için kullanılır. Bellekten temizler ve atanacak ya da set edilecek değişkenler var ise onları set eder. Nesne yönelimlideki destructor gibi davranırlar. Bunlar genelde close____ veya destroy___ şeklindedirler.

ÖR :
void fclose(FILE *);


Handle sistemleri kütüphane yazarken çok yardımcı bir araçtır. Handle sistemi ile ilgili bir örnek düşünürsek.

Örneğin bir kare ile ilgili işlemler yapan kütüphane yazacağız. Bunun için kare isimli bir handle 'a ihtiyacımız var. Biz bu handle 'ı müşteriye vererek setArea getArea vb. fonksiyonlar ile müşteriye istediği tüm değerleri verebiliriz. Bu zorlama ve basit bir örnek olduğu kanaatimdeyim daha güzel fikirler sizlerde üretebilirsiniz örneğin matrix ile ilgili olabilir bir şirkette çalışanları tutan olabilir.

// kare.h 



#ifndef    _KARE_H_

#define   _KARE_H_



typedef struct tagKare{

    //Kenarları

    int a, b, c, d;



}Kare;



int getArea(Kare *);

int setEdge(int, int, int, int, Kare *);

//buraya bircok fonk yazılabilir



#endif





// kare.c

#include "kare.h"



int getArea(Kare *k){

    return (k->a) * (k->b) * (k->c) * (k->d);

}



int setEdge(int a, int b, int c, int d, Kare *k){

    k->a = a;

    k->b = b;

    k->c = c;

    k->d = d;

}



Kısa fakat anlaşılır bir örnek olduğunu düşünüyorum sizlerde bunun gibi verdiğimiz matris örneğini yapabilirsiniz.
Bugünde yazımın sonuna gelmiş bulunmaktayım hepinize bol bilgili günler.




5 Aralık 2013 Perşembe

C 'de Dinamik Bellek Yönetimi

    Arkadaşlar bugün C'de dinamik bellek olayına el atıcaz. Öncelikle dinamik bellek yönetimi neden gerekli ve ne işe yarar onu ele alalım. C 'de nesneler bellekteki yerlerini derleme zamanında (compile time) ayırırlar. Yani nesne eğer 4 bayt ise derleme zamanında bellekte 4 bayt ayrılır ve çalışma zamanında(run time) bunu değiştiremezsin. Fakat dinamik bellek yönetimi ile çalışma zamanında bellekte yer ayırmayı öğreneceğiz yani bir dizi tanımlarken dizinin boyutunu kullanıcıdan almak veya çalışma zamanında bellekte ayrıca bir değişken tanımlama olanağımız olacak. Bunun için belli başlı standart C fonksiyonlarını kullanacağız. Kısaca bugün dinamik ömürlü nesneleri işleyeceğiz.
    Konuya başlamadan önce maliyet olayına değinelim. Derleme zamanında yer ayırmak çalışma zamanından yer ayırmaktan daha az maliyetlidir. Buda demek oluyor ki gerek olmadığı sürece dinamik ömürlü nesne kullanmak gereksiz ve maliyetli.
    Konumuza dönüp hangi standart fonksiyonları inceleyeceğiz bir bakalım. Fonksiyonlarımız;

    /* Prototipler*/ (Function Prototype)
    Malloc :    
void* malloc (size_t size);

    Calloc :     
void* calloc (size_t num, size_t size);

    Realloc :   
void* realloc (void* ptr, size_t size);

    Free :        
void free (void* ptr);


    ! size_t işaretsiz tamsayı (unsigned int) türüne typedef edilmiştir.
    Fonksiyonların protiplerini gördüğümüze göre ne iş yapar nasıl kullanılırlar bir bakalım.

    Malloc :    
void* malloc (size_t size);


-Malloc fonksiyonu bellekte yer ayırmak için kullanılır. Parametresi ayrılacak bellek boyutudur. Parametre size_t ye typedef edilmiştir size_t sizeof operatörnün geri dönüş değeridir. T türüne göre kullanımı şöledir (T herhangi bir tür belirttiği varsayılırsa)

T *ptr;
ptr = (T *)malloc(sizeof(T));  /*burada fonksiyon T türünün kapladığı yer kadar alanı bellekte ayırmıştır*/

Şimdi kodu incelersek. T * türünden bir değişken tanımladık bu değişken T türünden bir adres turuyor. malloc ile bellekte T türünün kapladığı alan kadar yer ayırdık ve pointerımıza bu bölgeyi tutturduk. Böylece *ptr ile o bölgeye istediğimiz atamayı yapabilmekteyiz.

Anahtar Notlar : 
-malloc fonksiyonundan sonra if (ptr == NULL) ile bir kontrol yapmalıyız. malloc eğer bellekte yer ayıramaz ise NULL adres ile geri döner.

- malloc ile ayırılan yer atama yapmadan kullanılmamalıdır. Undefined Behavior bkz. 'a neden olursunuz. malloc ayırıdığı yeri set etmeden geri döner yani çöp değer ile. (calloc set ederek geri döner)

-malloc fonksiyonunun parametresine sizeof girilmesi taşınabilirliği sağlar örneğin int her sistemde 4 bayt olmak zorunda değildir.

-malloc ile ayrılan yer program sonlanmadan free edilmelidir.(free yazının sonunda işlenecektir)

-malloc fonksiyonunun geri dönüş değerini atarken cast etmemek.(ptr = (int *)malloc...)
...

   Calloc :    
void* calloc (size_t num, size_t size);


-Calloc fonksiyonuda malloc gibi bellekte yer ayırır. Parametrelerinden birincisi kaç adet ayrılacak sorusunun cevabı, ikincisi ise ayrılacak bir adet değişkenin bellekteki kapladığı yerdir(sizeof'u). Geri dönüş değeri ise ayıramazsa NULL adres ayırırsa ayrıdığı yerin adresi(birden fazla ayrılacaksa ilk adres dizi mantığı). Calloc malloc ile aynı işlemi yapmaktadır yani malloc yerine calloc calloc yerine malloc kullanmak birşey değiştirmez fakat calloc ayırdığı bellek gözünü 0 ile set eder. Bunun zorunlu olduğu yerlerde calloc kullanılır yada malloc kullanıldıktan sonra memset ile set edilir.

int türüne göre kullanımına bir örnek;

int x, i;
int *ptr;
printf("Kac adet tamsayi ayıralım\n");
scanf("%d", &x);

ptr = (int *)calloc(x, sizeof(int)); //calloc ile yer ayırdık

if (ptr == NULL)   // yer ayırma işleminin başarı kontrolü
    printf("yer ayıramadık");

for (i = 0; i < x; ++i)   // dizimizi yazdırdık
    printf("%d", ptr[i]);

/* ekrana bütün elemanları 0 basacaktır*/



    Realloc :   
void* realloc (void* ptr, size_t size);


-Evet arkadaşlar bellekte yer ayırmayı öğrendik peki programımızda ayırdığımız yeri büyütmemiz, küçültmemiz istenirse ne yapacağız. İşte burada devreye realloc fonksiyonumuz girmektedir. Bu fonksiyon bellekte ayrılan belleği büyültmeye, küçültmeye veya daha doğru olarak yeniden ayırmaya yarar (neden yeniden ayırma dediğimizi anlatacağım). Şimdi parametreleri inceleyelim, birinci parametremiz ayrılmış bellek bloğunun adresini istemektedir, ikinci parametremiz ise yeni belleğin boyutu.

int türüne göre kullanımına bir örnek yaparsak;

int x, y, i;
int *ptr;

printf("Kac adet tamsayi ayıralım\n");
scanf("%d", &x);

ptr = (int *)calloc(x, sizeof(int)); //calloc ile yer ayırdık

if (ptr == NULL) // yer ayırma işleminin başarı kontrolü
    printf("yer ayıramadık");

printf("Kac adet arttıralım"\n");
scanf("%d", &y);

ptr = (int *)realloc(ptr, (x + y) * sizeof(int)); // bellekte boyutumuzu y kadar arttırmış olduk

if (ptr == NULL)

    printf("buyutemedik");







Anahtar Notlar : 
-realloc bellekte tekrar yer ayırdığında aynı bellek gözünü büyültür diye bir garanti vermemektedir. Eğer başka bir gösterici ile bu bellek bloğunu gösteriyor isek pointer'ı güncellememiz gerek güncellemezsek invalid pointer (kendisi hayatta gösterdiği yer hayatta olmayan pointer) olur. Pointer 'ı dereferens ettiğimizde Undefined Behavior bkz. durumuna düşeriz.

-realloc fonksiyonuna birinci parametreyi NULL adres gönderirsek malloc gibi davranır.

-Aynı yeri büyütmek zorunda değildir kopyalar ve eski yeri free eder.

-realloc büyütemezse NULL adres ile geri döner

-ikinci parametreyi 0 girersek free görevi yapar. (Sanırım bu durum son standartlarla güncellendi araştırıp yazıyı güncelleyeceğim).

...

    Free :        
void free (void* ptr);


-Artık bellekte yerlerimizi ayırtmayı büyütmeyi küçültmeyi öğrendik artık bu yerleri serbest bırakmayı öğrenme vaktimiz geldi. Free fonksiyonu ismi gibi parametre olarak gönderilen bellek gözünü serbest bırakır. Eğer bırakmazsak ne olur derseniz bellek sızıntısı (memory leak) meydana gelir. Çünkü program serbest bırakmadığı bir yeri tekrar kullanamaz. Bu nedenle free etmek (serbest bırakmak) zorunludur. Protoripi incelersek geridönüş değeri void yani yoktur, parametresi ise free edilecek bellek gözüdür. Kullanımı şu şekildedir.

T *ptr;
ptr = (T *)malloc(sizeof(T));  //burada fonksiyon T türünün kapladığı yer kadar alanı bellekte ayırmıştır

if (ptr == NULL)
    printf("yer ayıramadık");

free(ptr); //bellek gözünü serbest bıraktık

Anahtar Notlar : 
-free ettikten sonra ptr 'ye erişmemek gerekir.

-Dinamik olmayan bellek bloğu free edilmemelidir.

-İki kere free edilmemelidir.

-Parametre olarak NULL adres girmenin bir zararı yararı yoktur.
...

Yazımızın sonuna gelmiş bulunmaktayız umarım faydalı olabilmişimdir. Belirtmeyi unuttuğum yerler az da olsa olabilir tekrar okuyup güncellemeleri yapacağım. Hepinize teşekkürler hatam oldu ise affola.


1 Aralık 2013 Pazar

C 'de Hata Ayıklama (Assert Makrosu)

Programlamanın en önemli kısımlarından biride hata ayıklama(debuging) bölümüdür. C 'de hata ayıklama işlemi için bize mükemmel olmasa da bizi idare edebilecek çoğu kişinin kullanmadığı bir makro ile gerçekleşmektedir. Öncelikle makrolar nelerdir daha özel adıyla function like macro(fonksiyon benzeri makro)'lar nelerdir onlara kısaca bir bakalım.

Fonksiyon Benzeri Makrolar(Function Like Macro) : Burada define komutlarını ve önişlemci(preprocessor) programı biliyormuşuz gibi anlatacağım. Function like macro 'lar da object like macro'lar gibi bir define komutu ile oluşturulur. Fonksiyonların çağırılmasındaki gibi bir maliyetleri yoktur. Sadece "bul-değiştir" işlemi ile derleme zamanından(compile time)'dan önce değiştirilirler. Derleyici(compiler) bu fonksiyon benzeri kodları görmez yerine geçenleri görür. Assert de fonksiyon değil makrodur. Bir örnekle gösterirsek.

#define       arraysize(a) (sizeof((a)) / sizeof(*(a)))  //dizinin size'ını bulan bir makro

bu durumda önişlemci arraysize(dizininadı) gördüğü yeri (sizeof((dizininadi)) / sizeof(*(dizininadi))) olarak değiştirecek.
NOT : parantezler operatör önceliğinden hata almamak için gerekli! Çünkü makro bir işlem içinde kullanılabilir.

Assert : Gelelim artık assert makrosuna bu makro kontrol durumlarında işimize yarayacak. Yani örneğin en basitinden 0'a bölme hatası yapmamak adına bu makroyu kullanabiliriz. Bu makro şöle çalışmaktadır.
assert(exp) yani assert(x != 0) gibi hata olabilecek durumlar assert makrosunun içine yazılır. Hata var ise ekrana hata mesajı yazar ( bu arada stderr 'e yazar) ve abort() fonksiyonunu çağırarak programı sonlandırır.

Peki debug işlemimiz bitti ve assert makrolarını koddan temizlemek zor geliyor. Bunu da düşünmüşler temizlemenize gerek yok. assert.h'nın include 'undan önce NDEBUG 'ı define eferseniz assert makroları yerine assert(ignore)((void) 0) yerleştirilecektir.

Bu nedenle assert içinde işlem yapmak sakıncalıdır. NDEBUG define edip assert'ü kaldırmak isterken kodu bozarız örnek olarak

int *ptr;
assert(ptr = (Tür*)malloc(sizeof(Tür)));

hatadır NDEBUG define edip alt kodlarda ptr kullanırsak ptr değer almadığından undefined behavior olur.

Bugünlükte yazımızın sonuna geldik umarım faydalı olmuştur. Bir hatam olduysa affola herkese iyi okumalar :)

29 Kasım 2013 Cuma

C'de Undefined-Unspecified-İmplementation Dependent Behavior Kavramları

    Geçen gün araştırmak isterken bu kavramların genelde C derslerinde pek geçmediğini farkettim. İnternette yapılan C dersleri genelde "if else while for ve bitti" şeklinde. Fakat malesef C yapacağınız en ufak hatayı affetmeyecek türden bir dil derleyiciden geçip hata alma olasılığınız veya programınızın doğru çalışmama olasılığı var. Bu nedenle C 'nin standartlarını okumakta en azından bir özetine bakmakta fayda var. Fonksiyon dökümantasyonlarını incelemekte de fayda var (bkz. Strtok fonksiyonu dökümantasyonu) veya Standart C Library diyerek internette arattığınızda kitabı veya pdf versiyonunu edinebilirsiniz. Gelelim C 'de yapmamamız gereken araştırmamız gereken hatalara. Bu kavramlar şunlardır; Undefined Behavior, Unspecified Behavior, İmplementation Dependent Behavior. Şimdi bunları şöyle bir özetleyelim.

Undefined Behavior : Bu kavramda yazdığınız program syntax olarak tamamen legaldir fakat tanımsız davranışa yol açan bir kod elde etmiş olursunuz. Bu olayın en tehlikeli kısmı derleyicinin size herhangi bir hata vermemesidir (Warning ile uyarabilir) ayrıca derleme aşaması başarıyla geçilir. Fakat Runtime'da programın hiç bir garantisi yoktur çökebilir pointer işlemlerinde daha kötü sonuçlar doğurabilir. Bu konuda örnekler verirsek.

String Lateral'lerin değiştirilmesi!

char *s = "guven";
s[0] = 'a';     //Undefined Behavior

Atanmamış pointer'ı dereferens etmek!

int *ptr;
int x;
x = *ptr; //Undefined Behavior

Undefined behavior'lar C Standartlarında belirtilmişlerdir.

Unspecified Behavior: Bu kavramda sonuçları bellidir fakat bir kaç tanedir. Yani özetlersek bu durumları göz önüne alarak program yazmak hatadır. Bu kavramda durumlar derleyiciye göre değişkenlik gösterebilir ve derleyici yazanlar hangi seçeneği seçtiklerini dökümante etmek zorunda değildirler. Bu konuda C99 Standartları belgesinde verilen bir örneği kullanmak istiyorum. "Argümanların parametrelere kopyalanma sırası sağdan sola soldan sağa olabilir." Burada dikkat edilmesi gereken nokta " , " operatörünün yan etki noktası görevi görüp görmeyeceği yani örnekle açıklarsak ;

int foo(int, int);

//fonsiyon çağırısı

foo(x, x++);

şeklinde olursa ++ operatörünün görevini tamamlayıp tamamlayamayacağı tamamen derleyiciye bağlıdır. Kopyalamaya sağdan başlarsa artış olur soldan başlarsa artış olmaz.

İmplementation Dependent Behavior: Bu kavram unspecified behavior'ın belgelenmiş hali olarak tanımlanabilir. Durumun bir kaç sonucu vardır bu sonuç derleyiciyi yazana bırakılmıştır fakat derleyiciyi yazanlar bu durumları dökümante etmek zorundadırlar. Örnek olarak işaretli sayıda sağdan ötelemede işaret bitinin korunması İmplementation Dependent Behavior'dır.

Bu yazımızın da sonuna gelmiş bulunmaktayız umarım yazım sizlere faydalı olabilmiştir. Bu blogda artık if else yerine C 'nin ve programlamanın daha farklı kısımlarını anlatmaya çalışacağım tabi ki elimden geldiğince :) Bir hatam var ise affola teşekkürler.