Bu site emekli olmuştur. Arşiv amaçlı olarak BT AKADEMİ sponsorluğunda yayın hayatına devam etmektedir.




C#nedir?com
 
YAZAR HAKKINDA
Oğuz Yağmur
Oğuz Yağmur
http://www.oguzyagmur.com
İletişme geçmek için tıklayın.
26 Makalesi yayınlanmakta.
Yazar hakkında detaylı bilgi için tıklayın.
Yayınlanan diğer makaleleri için tıklayın.
İlgili etiketler: basinda degerini derleyici durumda hatalari hatasi malloc mesajlari nedeni olacaktir. oldugunu program programimiz return yanlis C / Sys Prog. Oğuz Yağmur
 
YAZI HAKKINDA
Türü : Makale
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
Seviyesi : Başlangıç
Kategori : C / Sys Prog.
Yayınlanma Tarihi : 28.1.2007
Okunma Sayısı : 34195
Yorum Sayısı : 2     yorum yaz
Site İçi AramaSİTE İÇİ ARAMA
Üye Girişini AçÜye GİRİŞİ
Üye girişi için tıklayın.
Kullanıcı Adı
Şifre
 
Beni her zaman hatırla
Bir hafta boyunca kullanıcı bilgilerinizi kullanıcı çıkışı yapana kadar hatırlar. (Paylaşılan bilgisayarlarda önerilmez.)
 
Şifremi / Kullanıcı Adımı unuttum.
 
.net TV RSS Serbest KÖŞE (?)
Serbest Köşede C#nedir?com üyelerinin hazırladıkları yazılar yayınlanır. Bu yazılar editör incelemesine girmeden yayınlanır.
emre TAŞ
Silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
emre TAŞ
silindi
emre TAŞ
yazının devamı >
Makale Gönder Bende Yazmak İstiyorum
.net TV RSSBlogroll
Turhal Temizer
Conda install environment.yml Package 21.11.2024
Turhal Temizer
Mac OS/X Removing CUDA 21.11.2024
Burak Selim Şenyurt
Rust ile ECS Yaklaşımını Anlamak 21.11.2024
Burak Selim Şenyurt
Birlikte Rust Öğrenelim Serisi 21.11.2024
  Diğer Herşey
Sponsorlar
BT Akademi
Medya Portakal
Video Hosting Sponsoru
Csharpnedir.com bir Ineta üyesidir
Uzman Abi
Her Yönüyle C# - Sefer Algan
Hata Tespiti ve Çözümlenmesi
 
Kapat
Sayfayı Yazdır Sık Kullanılanlara Ekle Arkadaşıma Gönder MySpace Del.Ico.Us Digg Facebook Google Mixx Reddit StumbleUpon
"Oof offf offffff!!!! yine ne oldu! nerde yanlış lık yaptım ben şimdi? yok yok bu derleyicide bi yanlışlık var sapıtıyor bazen. Şeytan diyor sil baştan yaz şunları şimdi". Birçok defa buna benzer serzenişlere figüranlık etmişiz ya da başrolde biz oynamışızdır. Malum hatasız kod pardon programcı olmaz. Aslında programlamanın %10’u esin %90’nı ise hata ayıklamaktan ibaret olduğunu kabullenmek gerekiyor. Tanıdığım çok iyi programcıların en güçlü özelliklerinin başında çok iyi hata ayıklama becerilerinin gelmesidir. Bu yazımızda en sık karşılaşılan hatarın nedenlerini inceleyerek hatalarımızı en aza indirmeye çalışacak metodları gözden geçireceğiz.

Artırma ve Eksiltme Operatörleriden Kaynaklanan Hatalar

En sık kullandığımız operatörlerin başında ++ ve -- operatörleri gelir. Ama çoğu zaman operatör öncelik sırasına, değişkenin önünde (prefix) veya arkasında (postfix) kullanılmasına dikkat etmeyiz. Tabiki bunlarda hatalara yol açacaktır. Örneğin;

1.Örnek                  2.Örnek
a = 10;                   a = 10;
b = a++;                 b = ++a;


Dikkat ettiyseniz yukardaki iki örnek aynı sonucu vermeyecektir. Birinci örnekte 10 değerini b’ye atar ve sonrasında a’yı artırır. İkinci örnekte ise a’ nın değerini artırır ve 11 değerini b’ye atar. Eğer ++ veya -- operatörlerinin önek ya da sonek olmasından kaynaklanan değişiklikleri gözardı edilmesi durumdan problemler ortaya çıkacaktır.

Opertör önceliklerinin dikkate alınmaması yine benzer problemlere yol açacaktır. Örneğin;

1.Örnek                   2.Örnek
c = a + b;                c = ++a + b;
a = a + 1;


Yukardaki iki örnek farklı sonuçlar üretecektir. Nedeni ise ikinci örnekte a ile b toplanmadan önce a’nın değerinin artırılmasıdır (operatör önceliğinden dolayı).

Bazen bu tür hataların bulunması nerdeyse işkence haline gelebilir. Bu tür hataların yakalanmasında düzgün çalışmayan döngüler ya da normal değerinden 1 fazla olan rutinler bize ipucu olabilir. Eğer bu tür ifadelerde bir şüphe duyuyorsanız bunları emin olacağınız şekilde yeniden kodlamak hataları çözmeye katkıda bulunacaktır.

Gösterici Hataları

"xxx.exe bir sorunla karşılaştı ve kapatılması gerekiyor..." Ne çok görmüşüzdür bu programımızın çöktüğünü belgeleyen sevimsiz hata mesajını. C programcılarının en çok karşılaştığı hatalardan biri de göstericilerin yanlış kullanılmasıdır. Bir örnek ile incelemeye başlayalım:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *ptr;
   
    *ptr = (char *) malloc(1000); /*hatalı kod * /
    gets(ptr);
    printf(ptr);
   
    return 0;
}


Yukardaki program çok büyük ihtimalla çökecektir. (yine o sevimsiz hata mesajını göreceğiz!) Nedeni ise malloc tarafında tahsis edilen alanın adresini ptr göstericisine atamak yerine ptr göstericisinin gösterdiği alana atama işlemi yapılmasıdır. Ancak sorun, ptr göstericisinin gösterdiği alan kesinlikle bilinmemekte ve bizim kontrolümüz dışındadır. Bu hata genelde C ile programlamaya yeni başlayanlarda (ki büyük olasılıkla * operatörü yanlış anlaşılmıştır) ve büyük olasılıkla o an ya TV seyrederken kod yazan ya da uykusuzluktan gözünü açamayacak halde iken kod yazan profesyonel programcılar tarafından yapılır. Programızı düzeltmek için aşağıdaki değişikliği yapalım.

ptr = (char *) malloc(1000); /* Geçerli kod */

Peki bu değişikliği yapmakla acaba kodumuz bütün hatalardan arındı mı? Hayır diyenler büyük çoğunlukta olsa da "hmm daha ne var ki düzeltilecek" diyenler de olacaktır. O zaman bende "Evet!" diyenlere şunu sormak istiyorum. malloc ile tahsis etmek istediğiniz alanı gerçekten tahsis edebildiğinizden emin misiniz? malloc ile tahsisat yapmak istediğimizde eğer yeterli alan yoksa malloc NULL değerini döndürecektir. ptr göstericisi NULL değere sahip olacak ve ptr yi kullanmak istediğimizde programımız hemen çökecektir. Bu hatanın önlenmesi için malloc fonksiyonunun işlemi başarı ile tahsisat yapıp yapmadığını kontrol etmemiz gerekir. Kodumuzun düzeltilmiş hali aşağıdaki gibi olmalıdır.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *ptr;

    ptr = (char *) malloc(1000);
    if(!ptr) {
        printf("Yetersiz bellek alanı!!!\n");
        exit(1);
    }
    gets(ptr);
    printf(ptr);

    return 0;
}


Bir başka yaygın hata ise, bir göstericiyi kullanmadan önce göstericiye ilk değer verme işleminin yapılmamamış olmasıdır. Örneğin;

int *pX;
*pX = 100;
/* Hatalı kod*/

Yukardaki kod muhtemelen 5 - 10 satır aşağıda sorunlara neden olacaktır. Çünkü pX in nereyi gösterdiğini bilmiyoruz ve bilmediğimiz bir alana atama işlemi yapıyoruz(Ben ambulans sesleri duymaya başladım ya siz?). Bilmediğimiz alan belki başka bir kod bölgesi ya da veri ise o zaman neler olabileceğini siz düşünün! Kontrolümüzde olmayan işaretçilerin farkına varılması ve takip edilmesi çok zor ve zahmetli işlerdir. Bazen gösterici hatası yapsak bile tamamen raslantısal olarak programımız düzgün çalışabilir ve hatanın farkında olmayabilirsiniz(aslında işletim sistemi 112 yi çoktan aramış, ambulanslar yoldadır bile). Ama programımız büyümeye başlayınca hele de programınıza yeni fonksiyonlar,elemanlar eklemişseniz iş daha da karmaşıklaşacak, hataları bunlarda aramaya başlayacaksınız ki işler iyice arap saçına dönecektir. Bir gösterici hatası olduğunu nasıl tespit edeceğiz o zaman? Genellikle bu gibi durumlarda programımız tutarsız davranacak, bazen doğru bazen de yanlış çalışacaktır. Bazen de sonuçları hiç alakasız değerlerle görebiliriz(Mesela biz ekranda "abc" yazısını görmeyi beklerken "xnj8399-9ş*7ş87*..." gibi bir değer görebiliriz). Bu gibi durumlarla karşılaştığımızda öncelikli olarak göstericilerimizi tekrar gözden geçirmemiz gerekir. Şimdi bunlardan sonra "Ne yani gösterici kullanmayalım mı diyosun sen?" gibi sesler duyabilirim diye şunu belirtmeliyim ki göstericiler C’nin en güçlü yönlerinden biridir ve ne kadar hataya yol açarlarsa açsınlar göstericileri kullanabilme yeteneğimiz herşeye değecektir. Göstericileri tam anlamı ile kavramak birçok hatının önlenmesinde katkı sağlayacaktır.

Söz Dizimi Hataları

Çoğu zaman öyle hatalarla karşılaşırız ki "ya bu derleyici hangi dilden konuşuyo acaba" dedirtecek türden hatalardır. Derleyici tarafından size gösterilen hata sizin yazdığınız kod ile alakası yoktur. Ama şunu da unutmamak gerekir ki derleyici her zaman haklıdır. Tamam hata mesajları çok da mükemmel değildir ama tespitte haklıdır. Mesela aşağıdakli örnekte can sıkıcı bir hata alacağız;

#include <stdio.h>

char *func(void);

int main(void)
{
    /**********/
    return 0;
}
int func(void)
{
    printf("func\n");
    return 1;
}


VC7 ile alınan mesaj :
error C2040: ’func’ : ’int (void)’ differs in levels of indirection from ’char *(void)’
GCC ile alınan mesaj :
errmsg.c:12: error: new declaration `int func()’
errmsg.c: 3: error: ambiguates old declaration ’char* func()’
Bazı derleyicilerde de : Type mismatch in redeclaration of func(void)

şeklinde hata mesajları alabilirsiniz. Peki nasıl oluyorda bu mesajları alıyoruz? Bizim iki tane func fonksiyonuzmuz yok ki!. O zaman nerden çıktı bu mesajlar! Kodumuzu yakından incelersek, kodumuzun başında func fonksiyonun geri dönüş değerinin char türünden bir gösterici olduğunu görürüz. Bu durumda derleyici prototip bildirimini gördüğünde bu bilgileri sembol tablosuna yazacaktır. Daha sonra programımızın içinde func ile karşılaştığında geri dönüş değerinin int olduğunu görecek ve bize "yeniden bildirimini yapıyorsun bu fonksiyonun" ya da "yeniden tanımlıyorsun bu fonksiyonu" diyecektir. Buna benzer bir söz dizimi hatası da şöyledir.

#include <stdio.h>

void func(void);

int main(void)
{
    /**********/
    func();
    return 0;
}

void func(void); /* Hatalı kod */
{
    printf("func\n"); 
}


Buradaki hata ise func fonksiyonunun tanımlanmasından sonra gelen ; kullanılmasından dolayı oluşan hatadır. Yine derleyiciden derleyiciye farklı hata mesajları alabilirsiniz. Ama çoğu derleyici ; kullanılmasından dolayı bunu bir bildirim sanacak ve ; den sonra gelen açılış küme parantezini işaret ederek "bad decleration syntax" gibi bir hata mesajı ile sizi uyaracaktır. Genelde ifadelerden sonra noktalı virgül görmeye alışık olduğumuz için bu gibi hataları saptamak çok zor olacaktır. (Birdefasında if(a == 0) yerine if(a = 0) yazılan bir kodda, 2 saat boyunca hata aradığım olmuştur!!!)

Dizi Uzunluğunda Yapılan Hatalar

Bildiğimiz gibi C’de dizilerin indeksleri 0 dan başlar. Ama çoğu zaman deneyimli programcıların bile bu özelliği unuttuğuna şahit olmuşuzdur. Örneğin uzunluğu 100 olan int türünden bir diziye değer atayan aşağıdaki örneğe bakalım.

#include <stdio.h>

int main(void)
{
    int ind;
    int dizi[100];

    for (ind = 1; ind <= 100; ++ind)
        dizi[ind] = ind;< BR >
    return 0;
}


Tabiki bu örneğin çalışmayacağını farketmişsinizdir. Programımızda iki tane yanlışımız var. İlk yanlışımız dizi[0]’a ilk değerin verilmemiş olması, ikincisi ise dizinin sonundan bir adım ileriye değer ataması yapılmıştır çünkü dizi[99] dizimizin son öğesidir. n elemanlı bir dizinin 0 dan n-1 e kadar elamanı olduğunu aklımızdan çıkarmazsak hataları önlemiş oluruz.

Sınır Hataları

C’nin standart kütüphane fonksiyonları ve çalışma ortamı çok az sınır kontrolü gerçekleştirir ya da hiç gerçekleştirmez diyebiliriz. Mesela bir önceki konudaki gibi dizi sınırlarını çok rahat bir şekilde aşabiliriz. Mesela, bir programımız olsun, programımız klavyeden bir karekter katarı alsın ve onu ekranda görüntülesin. Programımız şu şekilde olacaktır:

#include <stdio.h>

int main(void)
{
    int x;
    char dizi[10];
    int y;

    x = 10;
    y = 10;
    gets(dizi);
    printf("%s %d %d", dizi, x, y);

    return 0;
}


Yukardaki örnekte ilk bakışta bir kodlama hatası yok gibi görünse de gets()’i dizi ile kullanarak çağırmak ilerde hatalara neden olabilir. Programımızda dizi 10 karakter alacak şekilde bildirimi yapılmıştır. Ama kullanıcı 10 dan fazla karakter girince ne olur? Tabiki bu dizi’nin taşmasına neden olacak ve x yada y’yi veya herikisini birden ezecek sonuçta x ve y doğru değerleri içermeyecektir. Bunun nedeni ise bütün C derleyicileri yerel değikenleri depolamak için yığını (stack) kullanıyor olmalarıdır. Muhtemelen x,y,dizi bellekte sırası ile x, dizi, y şeklinde sıralanacaktır. Bu sıralamayı gözönüne alırsak, dizi taştığında fazladan girilen bilgiler y’ye ait olan alana yerleştirilecek böylece eski bilgilerin bozulmasına neden olacaktır. Tabi bunları hesap etmediğimiz takdirde, ekrana her iki değer için 10 yazmasını beklerken alaksız değerler yazılacak ve hataları başka yerde arayacağız(muhtemelen salak derleyicide yine bir sapıtma belirtileri olduğunu düşüneceğiz!!!). Bu sorunu ortadan kaldırmak için gets() yerine fgets() kullanmamız bir çözüm yolu olabilir(fgets() ile okunacak maksimum karekter sayısını belirleyebiliyoruz).

Fonksiyon Prototiplerinin Yapılmaması

Aslında programcılar biraz tembel insanlardır bunu kabullenmek gerekir heralde. Çoğu zaman yazdığı fonksiyonların prototip bildirimlerini yapmaktan üşenirler. Bu çok büyük hatalara neden olan bir karardır.C eğitimi aldığım (www.csystem.org) süreçte hocalarım sık sık bu konu üzerine eğilir öneminden bahsederler bu konuyu dikkate almamızı şiddetle tavsiye ederlerdi. Tabi ilk başlarda pek anlamasak da hatalı kodlarla boğuşurken bazı şeyleri kulağımıza küpe yaptık. Peki nedir bu kadar önemli kılan bu konuyu. Hemen bir örnek üzerinde incelemeye başlayalım.

#include <stdio.h>

 int main(void)
{
    float a, b;
   
    scanf("%f%f", &a, &b);
    printf("%f", carp(a, b));
   
    return 0;
}

double carp(float a, float b)
{
    return a * b;
}


Şimdi biz 2.2, 3.3 değerlerini girdiğimizde sonuç olarak 7.26 dödürmesini mi bekliyoruz? Malesef hayattan çok şey bekliyoruz. Biz carp() için prototip kullanmadığımızdan dolayı main(), carp()’tan bir tamsayı değeri döndürmesini bekler. Ama carp() double değeri döndürecek şekilde yazılmıştır. Peki biraz daha ayrıntıya girecek olursak, tamsayıların 4 byte, double’ların ise 8 byte olduğunu düşünürsek, printf() bu durumda 8 byte lık double için yalnızca 4 byte ını kullanacak böylece de biz ekranda yanlış değerlerle karşılaşmış olacağız. O zaman carp fonksiyonumuzun prototipi kullanarak main() e carp fonksiyonun double türünden değer döndüreceğini bildirerek problemi ortadan kaldırabiliriz. ( double carp(float a, float b); )

Argüman Hataları

Davul bile dengi dengine boşuna dememiş atalarımız. Bir fonksiyonun beklediği parametre tipi ile, fonksiyone verdiğiniz parameterelerin eşlendiğinden emin olmamız gerekir. Fonksiyon prototipleri kullanarak bu hataları öneleyebiliriz ama tüm hataları yakalamamız biraz zor. Nedeni ise, değişken sayıda parametre alan fonksiyonlarda tip uyumsuzlukların yakalanması mümkün değildir. Örneğin scanf() fonksiyonu. Bildiğimiz gibi scanf(), paramatrelerinin değerlerini değil adreslerini almayı bekler ancak bizi bunun için zorlamaz.

{
    int x;
    scanf("%d", x);
}

Yukardaki örnek kod bir güzel derlenip program çalışabilir hale gelecektir. Ama program çalıştığında hata üretmesine neden olacaktır. Çünkü bu kod ile x’in adresi değil x’in değeri aktarılıyor.
Yığın (Stack) Taşması

Bildiğimiz üzere derleyiciler, yerel değişkenleri, fonksiyonların dönüş adresleri ve fonskiyonlara aktarılan parametreleri depolamak için yığını kullanırlar. Hayatta hiçbir şey sonsuz olmadığı gibi yığında sonsuz büyüklükte değildir. Yığın taşmasının olduğu bir durumda bazen program tuhaf bir şekilde çalışıyor olsa da bazen de program çökecektir. Yığın taşmasındaki en büyük problem ansızın ve anlaşılmaz bir şekilde ortaya çıkmasıdır. Haliylende böyle hataları saptamak çin işkencesine dönebilir. Bu durumda şüpheleneceğimiz konularında başında kendi kendini çağıran (recursive) fonksiyonlarımızın yanlış kodlanmış olabileceğidir. Programımızda kendi kendini çağıran fonksiyonlar kullanıyorsak ve anlamsız hatalar ile karşılaşıyorsak, bu tür fonksiyonların çıkış noktalarını birkez daha gözden geçirme vakti geldiğine karar verebiliriz. Eğer bunda da hata yok ise programımız hatasız bir şekilde kodlandığında eminsek bu sefer de derleyicimiz destekliyor ise yığın için ayrılan bellek miktarını artırarak bir çözüm üretebiliriz.

Son Söz

Birçok derleyici beraberinde hata ayıklayıcı (debugger) bulundurur. Bu sayede kodumuzu adım adım çalıştırarak, durak noktalarını (break point) ayarlamamıza ve değişkenlerimizin içeriklerini görebilmemize olanak sağlar. Gerçi hata ayıklayıcısını kullanmak o kadar da kolay bir iş olmamasına karşın bize sağladığı faydaları göz önüne aldığımızda, hata ayıklayıcısını etkin bir biçimde kullanmak için harcayacağımız emek ve zamana değecektir. Ama bizler iyi bir programcı isek hata ayıklayıcısına güvenmeyip, sağlam tasarım ve ustalığı herzaman tercih etmeliyiz.

Bunlardan ziyade, sizlerinde hata tespiti ve ayıklama konusunda teknikleriniz vardır muhakkak. Program geliştirme sürecinde her zaman hata kontrolü yaparak ilerlemek başta programın gelişim sürecini olumsuz yönde etkiliyor gibi görünse de zaman ve maliyeti göz önüne aldığımızda aslında en iyi yöntem olduğu ortaya çıkacaktır. Eğer çalışan bir kodumuz varsa, daha sonra bu koda her eklentide, yeni oluşan kodu test edip hatalardan ayıklamak iyi bir yöntem olacaktır. Bu sayede programcının (yani bizler) hataları yakalaması kolaylaşacaktır. Çünkü muhtemelen hata yeni eklenen koda ait olacaktır.Bu konu ile ilgili Kaan Aslan’ın Sistem Yayıncılık’tan çıkmış C Yanlışları adlı kitabı okumanızı tavsiye ederim.

Hatasız kodlar yazmamız dileğiyle...

Makale:
Hata Tespiti ve Çözümlenmesi C ve Sistem Programlama Oğuz Yağmur
  • Yazılan Yorumlar
  • Yorum Yaz
ARA
22
2004
çok güzel..
HAZ
30
2004
Oldukça profesyonelce yazılmış bu makale ile, en iyi yazılımcıların dahi bazen dikkat etmediği yada gözden kaçırdığı noktalara değinilerek, daha usta yazılımcı olmaları hedeflenmiş. Ellerine sağlık Oğuz. Çok güzel bir makale.
Sayfalar : 1 
Yorum yazabilmek için üye girişi yapmalısınız. Üye girişi için tıklayın.
Üye değilseniz Üyel Ol linkine tıklayarak üyeliğinizi hemen başlatabilirisniz.
 
  • Bu Konuda Son 10
  • Eklenen Son 10
  • Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor