Basit bir C bilmecesi

taydin

Yönetici
Yönetici
#1
Aşağıda iki tane basit C programı var. Bunları çalıştırdığımızda ekrana ne yazılır? Programlar doğru çalışıyor mu? Çalışıyor diyorsanız, neden? Çalışmıyor diyorsanız, gene nedenini açıklayınız.

Programları derlemeden önce, sadece bakarak soruları cevaplamaya çalışın. Cevap bulamazsanız, derleyin, sonucu görün, ve sonra da sonucu yorumlayın.

Kod:
#include <stdio.h>

int buffer[5] = {1, 2, 3, 4, 5};

int main()
{
   int toplam = 0;
   unsigned int i;

   for (i = 0; i <= 5; i++)
   {
      toplam += buffer[i];
   }

   printf("toplam = %d\n", toplam);
}
Kod:
#include <stdio.h>

int main()
{
   int buffer[5] = {1, 2, 3, 4, 5};
   int toplam = 0;
   unsigned int i;

   for (i = 0; i <= 5; i++)
   {
      toplam += buffer[i];
   }

   printf("toplam = %d\n", toplam);
}
 
Son düzenleme:

turkuazaga

Kayıtlı Üye
#2
İki programı derledim. İlk program doğru çalışıyor ama ikinci program 30 binli bir toplam veriyor. Bu toplam da her çalıştırmada değişiyor. Çok tuhaf, iki program için de buffer içeriği aynı, nasıl farklı bir sonuç çıkabilir anlamadım. İkincisinin de doğru çalışması lazım.
 

taydin

Yönetici
Yönetici
#3
Programları yeterince dikkatli incelememişsin. İki program da sorunlu. Her iki programı da debugger altında çalıştır, adım adım. Her adımda gerçekleşen şeyin doğru olup olmadığından emin ol.
 

turkuazaga

Kayıtlı Üye
#4
İlk program doğru sonucu buluyor, 5 tane sayı topluyor ve sonucu ekrana yazıyor. Debugger ile de çalıştırmayı deneyeceğim.
 

taydin

Yönetici
Yönetici
#5
Bir program belli bir durumda doğru sonucu yazıyorsa, hemen bu programın doğru olduğunu söyleyemeyiz. Eğer bir program, hesaplama kapasitesi dahilinde olan bütün değerler için doğru sonucu veriyorsa ancak o programa "doğru çalışıyor" diyebiliriz. Şu örneği düşün:

"Verilen sayının karekökünü bulan bir program yaz" dedik. Ama programcı, verilen sayının karekökünü alacağına sayıyı 3'e bölmüş. Bu durumda sayı olarak 9 verirsek, program bize 3 cevabını verecek ve bu doğru cevap. Ama program doğru mu? Değil. 36'nın karekökünü hesaplattırsak, bize 12 diyecek :)

İki program da sorunlu. Debugger ile adım adım çalıştırıp her adımın doğru olduğunu teyit etmen lazım.
 

turkuazaga

Kayıtlı Üye
#6
Tamam şimdi gördüm sorunu. İlk bakışta farkedilmiyor ama her iki döngü de 5 defa değil 6 defa çalışıyor. Dikkatli bakmayınca for loop'taki <= ifadesi gözden kaçıyor. 6 defa toplama yapıyor, ilk programda 6'ncı sayı 0 olduğu için sonucu etkilemiyor, ama ikinci programda 6'ncı sayı her seferinde değişen bir değere sahip. O yüzden her seferinde farklı bir sonuç çıkıyor!
 

taydin

Yönetici
Yönetici
#7
Evet aynen problem o. Döngü, buffer'ın sınırlarını aşıyor. 0'dan 4'e kadar değerler için dönülmesi gerekirken, 0'dan 5'e kadar değerler için dönüyor. buffer[5] artık buffer'ın dışında kalıyor.

C ve C++'da global olarak tanımlanan değişkenler ve buffer'lar, derleyici tarafından 0 ile doldurulur. Böylece program çalıştığında, oradaki değerleri ilk başta hep 0 olarak bulur. O yüzden tesadüfen program doğru çalışıyor. Ama buffer global değil de, main fonksiyonunun içinde local (stack üzerinde) bir buffer olunca, iş değişiyor. Stack üzerindeki bir değişken veya buffer'a programın kendisi değer ataması yapmazsa, orada tamamen rastgele bir değer olacaktır.

İlk program tesadüfen doğru çalışıyor, ama örneğin programımız o global buffer'dan okuma yerine o buffer'a yazma yapsaydı, o zaman buffer'dan öteye birşeyler yazacaktı, ve o buffer'dan sonra tanımlanan başka global değişkenler olsaydı, onların içeriğini bozacaktı. Bu tip buffer hataları C/C++ da çok yaygındır, o yüzden buffer işlemlerinde sınırları aşmamak için çok dikkat etmeliyiz.
 

theroot

Kayıtlı Üye
#8
Yeni GCC ve LLVM ile buffer sınırlarını aşan program yapılarını tespit etme olanağı var. Olabilecek her durumu algılayabiliyor mu bilmiyorum, ama bu tip bariz problemleri algılayabiliyor. GCC'nin -fsanitize=address opsiyonuna bakınız.

Bu opsiyon sadece buffer sınır aşma problemleri değil, memory leak ve race condition denen problemlerde de faylalı olduğu belirtiliyor. Ama bu özellikleri şimdiye kadar kullanmadım.
 

taydin

Yönetici
Yönetici
#9
Bahsettiğiniz kontrolleri yapan bir de "valgrind" denen bir kütüphane var. Arada bir valgrind'i kullanıyorum, ama hep şöyle bir sıkıntı ile karşılaşıyorum: Üzerinde uğraştığım projelerde, bizim geliştirmediğimiz başka birçok kütüphane kullanmak durumundayız. Bu kütüphanelerin geliştirmesini yapan kişiler de valgrind kullanma konusunda çok hevesli olmadıkları anlaşılıyor, çünkü program çalıştırıldığında yüzlerce valgrind uyarı mesajı ile karşılaşılıyor. Bu uyarı mesajı seli içerisinde hangi mesajların doğrudan sizin yazdığınız program ile ilgili olduğunu hemen görmek mümkün değil, çok uğraştırıyor. Eğer herkeste valgrind kullanma disiplini olsa, veya -fsanitize kullanma disiplini olsa, o zaman hakikaten bu araçların olması bulunmaz nimet.

Ama bu sorun olsa bile, valgrind ve dediğiniz gibi -fsanitize'i arada bir yazdığımız program üzerinde kullanmalıyız ve verilen uyarılar doğrultusunda programı gözden geçirmeliyiz.
 

taydin

Yönetici
Yönetici
#11
Aslında valgrind ve @theroot un bahsettiği -fsanitize ile ilgili ayrı ayrı konular açıp bunlarla ilgili örnek senaryolar yapmak lazım. Bunu not alıyorum.
 
Üst