Her C programcısı aynı kısa hata listesiyle, genellikle ilk hafta ve zaman zaman on yıl sonra karşılaşır. Bunları kataloglamaya değer kılan şey, çoğunun kendilerini tarif etmeyen hata mesajları üretmesi - ya da daha kötüsü, hiç mesaj üretmemesidir.
Aşağıdaki her madde bir belirti, ardındaki sebep ve çözümdür.
Eksik Noktalı Virgül ve Zincirleme Etki
Belirti: hepsi doğru görünen satırlarda bildirilen bir hata yığını.
program.c:6:5: error: expected ';' before 'printf'
program.c:7:5: error: expected declaration specifiers before 'return'
Sebep: C deyimleri ; ile bitirir. Birini atlayın, derleyici bir sonraki satırı mevcut satıra yapıştırır, sonra birleşik metnin anlamını yitirdiği noktada - tipik olarak sonraki satırda - karışıklık bildirir.
int main(void) {
int x = 5 /* eksik noktalı virgül */
printf("%d\n", x);
return 0;
}
Çözüm: bir yığın hata belirdiğinde yalnızca ilkini düzeltin ve yeniden derleyin. Ondan sonraki her şey artçı sarsıntı olabilir. Ve her zaman derleyicinin adlandırdığı satırdan önceki satırı kontrol edin.
Aynı zincirleme etki, hataların onlarca satır ötede belirebildiği kapatılmamış bir süslü parantezden ya da sonlandırılmamış bir /* yorumundan da gelir.
Ek bir not: bir if, for, while başlığından ya da bir fonksiyon tanımının kapanış parantezinden sonra noktalı virgül olmaz. if (x > 0); sorunsuz derlenir ve hiçbir şey yapmaz - ; gövdenin tamamıdır.
Karşılaştırma Yerine Atama
Belirti: her zaman doğru olan bir koşul ya da gizemli biçimde değişen bir değişken.
int x = 5;
if (x = 10) { /* 10 atar, sonra 10'u test eder -> doğru */
printf("x on\n"); /* her zaman yazdırır; x artık 10 */
}
Sebep: = atar, == karşılaştırır. Atamanın değeri atanan değerdir, dolayısıyla if (x = 10) sıfırdan farklı, yani doğru olan 10'u test eder. Derleyici bunu kabul eder, çünkü zaman zaman insanların kastettiği şey budur.
Çözüm: her koşulda == kullanın ve derleyicinin uyarması için -Wall'ı açın:
warning: suggest parentheses around assignment used as truth value
Koşul içinde gerçekten atamayı kastediyorsanız - while ((c = getchar()) != EOF) ile sık görülür - fazladan parantezler bunu söyler ve uyarıyı susturur.
Bir Fonksiyonun Örtük Bildirimi
Belirti:
warning: implicit declaration of function 'printf'
warning: implicit declaration of function 'malloc'
...ardından bazen tuhaf çalışma zamanı sonuçları ya da bir bağlama hatası.
Sebep: derleyici, hiçbir bildirimi olmayan bir fonksiyona yapılan çağrıyla karşılaştı. C99 öncesi lehçelerde fonksiyonun int döndürdüğünü varsayıp devam ederdi; 64-bit sistemlerde bu varsayım, döndürülen bir göstericiyi 32 bite kırpar ve eksik bir <stdlib.h> malloc'u böyle bir çökmeye dönüştürür.
Çözüm: doğru başlığı ekleyin.
| Fonksiyon | Başlık |
|---|---|
printf, scanf, fopen | <stdio.h> |
malloc, free, atoi, rand, exit | <stdlib.h> |
strlen, strcpy, strcmp | <string.h> |
sqrt, pow, fabs | <math.h> |
isdigit, toupper | <ctype.h> |
time | <time.h> |
Kendi fonksiyonlarınız için aynı uyarı, birini tanımlamadan önce çağırdığınız anlamına gelir. main'in üstüne bir prototip koyun:
& Olmadan scanf
Belirti: program girdide çöker ya da hiçbir şey okumaz ve değişkeni değiştirmeden bırakır.
int age;
scanf("%d", age); /* eksik & : adresi değil değeri geçirir */
Sebep: scanf değişkeninizin içine yazar, dolayısıyla değişkenin adresine ihtiyacı vardır. age geçirmek, içinde ne çöp varsa onu teslim eder ve scanf bunu yazılacak bir yer sayar ki bu genellikle bir segfault'tur.
Çözüm: değişkenin önüne & - zaten bir adres olan dizi dışında her tür için:
Aynı ailede iki komşu tuzak: scanf'te bir double için %lf kullanın (oradaki %f, float demektir ve bir double'ın içine float kadar bayt yazmak onu yanlış bırakır) ve bir %s'yi her zaman bir genişlikle sınırlayın - 50 baytlık bir tampon için %49s - yoksa uzun bir kelime onu taşırır.
Daha da iyisi: tüm satırı fgets ile okuyup ayrıştırın; bu taşamaz ve tamponda başıboş girdi bırakmaz. İkisi hakkında daha fazlası scanf sayfasında.
Metinleri == ile Karşılaştırmak
Belirti: açıkça eşleşen iki metin farklı olarak karşılaştırılıyor.
char a[] = "hello";
char b[] = "hello";
if (a == b) { /* iki adres karşılaştırılıyor: yanlış */
printf("ayni\n");
}
Sebep: bir C metni bir değer değil, ilk karaktere giden bir göstericidir. == göstericileri karşılaştırır. Aynı metni tutan iki dizi farklı adreslerde yaşar, dolayısıyla test yanlıştır. (Kafa karıştırıcı biçimde, iki özdeş sabiti karşılaştırmak bazen doğru verir, çünkü derleyici tek bir kopya saklayabilir - bu da hatayı aralıklı hâle getirir.)
Çözüm: strcmp ve eşitlik için 0 döndürdüğünü unutmayın:
Ters okuma da insanları yakalar: if (strcmp(a, b)), metinler farklı olduğunda doğrudur, çünkü sıfırdan farklı bir sonuç "eşit değil" demektir. Her zaman == 0'ı açıkça yazın.
Tam Sayı Bölmesi
Belirti: 0 çıkan bir ortalama, her zaman 0 ya da 100 olan bir yüzde, kesrini kaybetmiş bir oran.
int correct = 7, total = 10;
double score = correct / total; /* 0.7 değil, 0.0 */
Sebep: her iki işlenen de int'tir, dolayısıyla C, sonuç bir double'a atanmadan önce tam sayı bölmesi yapar ve kırpar. 7 / 10 sonucu 0'dır; 0'ı double'a dönüştürmek 0.0'dır.
Çözüm: bölmeden önce bir işleneni kayan noktalı yapın:
Bir işleneni dönüştürmek diğerini otomatik olarak yükseltir. Sonucu dönüştürmek çok geçtir - kırpma çoktan olmuştur. Bkz. tür dönüşümü.
Aynı tuzak, bir orta nokta için (a + b) / 2 ve x ne olursa olsun her zaman 0 olan 1 / 2 * x gibi ifadelerde gizlenir.
Eksik ya da Yanlış return
Belirti: bir fonksiyon, makul görünen yanlış bir sayı döndürüyor; her çalıştırmada ya da her derlemede farklı.
int add(int a, int b) {
int sum = a + b;
/* return deyimi yok */
}
Sebep: void olmayan bir fonksiyonun sonuna dönüş yapmadan ulaşmak belirtilmemiş bir değer verir - pratikte dönüş yazmacında ne varsa o. Çağıran onu kullanırsa bu tanımsız davranıştır.
Daha sinsi biçimi bazı yollarda döner, bazılarında dönmez:
int classify(int n) {
if (n > 0) return 1;
if (n < 0) return -1;
/* n == 0 sondan düşer */
}
Çözüm: her yolda dönün ve -Wall ile derleyin - GCC'nin "control reaches end of non-void function" uyarısı her iki sürümü de yakalar.
İlk Değersiz Değişkenler
Belirti: çöp çıktı ya da çalıştırmalar ve iyileştirme seviyeleri arasında değişen sonuçlar.
int total; /* yığında ne varsa onu içerir */
for (int i = 1; i <= 5; i++) {
total += i; /* çöpe ekleniyor */
}
printf("%d\n", total); /* devasa bir sayı */
Sebep: yerel değişkenler sıfırlanmaz. Bir küresel ya da static değişken otomatik olarak sıfıra ayarlanır; yerel olan, o yığın adresinde zaten bulunan baytlarla başlar.
Çözüm: bildirim noktasında ilk değeri verin. Hiçbir maliyeti yoktur ve tüm hata sınıfını ortadan kaldırır:
-Wall -Wextra bunların çoğu için uyarır ("may be used uninitialized") ve -fsanitize=memory ya da valgrind gerisini yakalar. Bu özellikle kötüdür, çünkü ilk değersiz bir gösterici doğrudan bir segmentation fault'a götürür.
Bir Eksik / Bir Fazla
Belirti: son eleman atlanır ya da bir eleman fazla dokunulur ve program daha sonra yanlış davranır.
int arr[5];
for (int i = 0; i <= 5; i++) { /* var olmayan arr[5]'e dokunur */
arr[i] = i;
}
Sebep: n elemanlı bir dizinin indeksleri 0 ile n - 1 arasındadır. <= fazladan bir geçiş yapar.
Çözüm: i < n kalıbı ve sayıyı iki kez yazmak yerine n'i diziden hesaplayın:
Metin sürümü - '\0' için yer bırakmayı unutmak - aynı hatanın başka bir şapkayla hâlidir ve içinde "hello" olan char word[5] bir tampon taşmasıdır.
Bilmeye Değer İki Küçük Hata
Döngü başlığından sonra noktalı virgül. Ardından süslü parantezli bir blok gelen for (int i = 0; i < 10; i++);, döngüyü hiçbir şey yapmadan on kez çalıştırır, sonra bloğu bir kez çalıştırır. Tertemiz derlenir.
Bir göstericide sizeof. Bir fonksiyonun içinde dizi parametresi bir göstericidir, dolayısıyla sizeof(arr) dizinin değil göstericinin boyutudur (8 bayt). Uzunluğu ayrı bir argüman olarak geçirin:
Bunların Çoğunu Önleyen Alışkanlık
Daha ilk programdan itibaren uyarılar açık derleyin:
gcc -Wall -Wextra -g program.c -o program
-Wall -Wextra; koşul içindeki atamayı, eksik dönüşü, ilk değersiz okumayı, kullanılmayan değişkeni ve argümanıyla eşleşmeyen printf belirtecini yakalar. Geliştirme sırasında -fsanitize=address,undefined eklemek geri kalanların neredeyse tamamını olduğu anda yakalar.
Her uyarıya, henüz çarpmadığınız bir hata gibi davranın. Uyarısız derlenen bir C programının doğru olduğu garanti değildir - ama çöken hemen her C programı önce bir şey hakkında uyarıyordu.
Sıkça Sorulan Sorular
C'de 'implicit declaration of function' ne demek?
Derleyici, bildirildiğini hiç görmediği bir fonksiyona yapılan çağrıyla karşılaştı. Neredeyse her zaman bir #include unutmuşsunuzdur - printf <stdio.h> ister, malloc <stdlib.h>, strlen <string.h>. Kendi fonksiyonunuzu tanımlamadan önce çağırdığınız anlamına da gelebilir; bunu main'in üstündeki bir prototip düzeltir.
C programım neden düzgün görünen bir satırda hata bildiriyor?
Genellikle asıl hata bir önceki satırda olduğu için. Eksik bir noktalı virgül, kapatılmamış bir süslü parantez ya da sonlandırılmamış bir yorum, derleyicinin iki satırınızı tek satır olarak okumasına yol açar, dolayısıyla karışıklığı ayrıştırılamaz hâle geldiği yerde bildirir. Her zaman önce bildirilen satırın üstündekini kontrol edin.
C'de metinleri neden == ile karşılaştıramıyorum?
Çünkü bir C metni bir char *'dır - bir göstericidir - dolayısıyla ==, gösterdikleri karakterleri değil iki adresi karşılaştırır. Farklı yerlerde saklanan iki özdeş metin false verir. <string.h>'deki, içerikler eşleştiğinde 0 döndüren strcmp(a, b) == 0'ı kullanın.
C'de 5 / 2 neden 2 veriyor?
Çünkü her iki işlenen de tam sayıdır, dolayısıyla C tam sayı bölmesi yapar ve kesri atar. 2.5 almak için bir tarafı kayan noktalı bir değer yapın: 5.0 / 2 ya da değişkenlerle çalışırken (double)a / b. Sonucu dönüştürmek çok geçtir - (double)(5 / 2) sonucu 2.0'dır.