PDA

Tam Sürümünü Görmek İçin : Bu scanf in, derleyicinin, kütüphanenin suçu mu?


mr_c
28/12/2006, 15:03
#include <stdio.h>
int main(int argc, char *argv[])
{
int sayi;
char harf;
printf("sayi girin: ");
scanf("%d",&sayi);
printf("devam(E/H) ? ");
scanf("%c",&harf);
return 0;
}

Yukarda çok basit bir C kodu var açıklanacak birşey de yok. Beklenen hareket bir sayı ve bir karakter okurmak.

Ama olmuyor! en azından eski TC ve yeni BloodShed derleyicilerinde. Kodu denemeden yorum yapmayın insanı aldatıyor.

Şimdi C den beklentilerimiz, standartlar la nasıl çelişiyor bunu tartışmak isterim.

İlginize teşekkürler.

Not: Link vermeyiniz. Nedenini buldum ama bu durum doğal mı onu tartışmak istiyorum


acehreli
28/12/2006, 22:10
Denedigim zaman su beklentilerin gerceklesmedigini gordum:

1) Ekrana "sayi yazin:" cikmadigi halde scanf sayi okuyor.

Bunun nedeni, C standardinin stdout akimina boyle bir serbesti vermesidir. Program, hizli calisma saglamak icin (veya herhangi bir nedenle) belirli bir olay olana kadar karakterleri cikisa gondermeme hakkina sahiptir.

Bu belirli olay pratikte '\n' karakterinin yazdirilmasi veya karakterlerin saklandigi ara bellegin (buffer) dolmasidir.

Bu konuda programcinin yapabilecegi bir sey, karakterleri acikca cikisa gondermek icin fflush'i cagirmaktir.

Ayni olay "devam(E/H) ? "in yazdirilmasinda da var.

[Ara not: C++'ta stdout'un esdegeri olan cout ile stdin'in esdegeri olan cin birbirlerine "baglanmislardir" (std::tie). O dilde cin'den okumak, once bekleyen karakterlerin otomatik olarak cout'a gonderilmesini saglar.]

2) "%c" ile harf'e okundugu yerde karakter giremiyoruz.

Bunun nedeni, %c'nin bosluk karaterlerini de okuma yetenige sahip olmasidir. %c, baska belirtecler gibi (ornegin %d) davranmaz: okumadan once giristeki bosluk karakterlerini gozardi etmez.

Biraz once sayi girilirken basilan Enter tusu girise '\n' karakterini yerlestirir ve %c bu karakteri okur.

Bunun onune gecmek icin programcinin yapabilecegi seylerden birisi, scanf'e bosluk karakterlerini atlamasini bildirmektir: %c'den once bir bosluk karakteri yazilir: scanf(" %c", &harf);

Soyle:


#include <stdio.h>

int main(int argc, char *argv[])
{
int sayi;
char harf;

printf("sayi girin: ");
fflush(stdout);
scanf("%d",&sayi);

printf("devam(E/H) ? ");
fflush(stdout);
scanf(" %c",&harf);

printf("okudum: %c\n", harf);

return 0;
}


Ali

mr_c
29/12/2006, 19:01
Not: Link vermeyiniz. Nedenini buldum ama bu durum doğal mı onu tartışmak istiyorum demiştim. Eski bir sorun yani ve çözümü de biliniyor sizin verdiğiniz iki örnekte olduğu gibi.

Kullanıcı varsayılan olarak char tipinde bir veri girmek ister. Ben char veri girmekten alfanümerik değer girmeyi anlarım. Ama bu durumda ascii 13 yani cr girişi de kabul edilmiş gibi görünüyor ve alfa nümerik değil.

Peki bu durum olağan madem üç adet CR girmek istiyorum (ENTER/RETUN). O da ne aşağıdaki kod da 3 kez entere basıyorum hala bişey olmuyor.

#include <stdio.h>
int main(int argc, char *argv[])
{
int sayi;
char harf[3];
printf("sayi girin:");
scanf(" %d",&sayi);
printf("devam(E/H) ? ");
scanf(" %s",harf);
return 0;
}

Yani kullanıcıdan alfa nümerik giriş alınabiliyor ama CR alanamıyor yada alınmak istemiyor.

ilk sorunlu koda dönelim. Ben çözümü değil bu durumun beklenen bir davranış mı olduğunu sormuştum. Beklesek de beklemesek de bu bu şekilde çalışıyor.

Burada bizim C den beklentilerimizi konuşmak isterim yani varsayılan davranışta anormallik var mı yok mu?

Tamponda girilen en son veri kalıyorsa neden kalıyor? Kullanıcı girişi yapmış ve giriş işemini bitirmiş tamponun silinmesi gerekmez mi? Tamponda veri varken ki işi daha önce bitmiş hala neden okuma yapılabiliyor yada yeni giriş işlemi kullanıcı tarafından girileceği zaten komutla bildirilmiş, burada çelişki varmı?

Streamlerle giriş çıkış amaçlı genel bir fonksiyon olduğunda bu mümkün denemez diğer standart giriş aygıtlarından okumak için read() fonksiyonu mevcut zaten sadece kullanıcı girdisi almak istersek kullanalım diye scanf() tanımlanmış.

scanf ile klavyeden 3 adet CR dizisi nasıl okuruz?
Bu davranışı C nin normal mi?

acehreli
29/12/2006, 21:33
Diger konuda da gordugum gibi, ve yalnizca soylediklerine bakarak, C'ye cok degisik bir yaklasimin oldugunu goruyorum. Onun icin ne kadar ilgisiz de olsa kucuk noktalara da deginecegim, cunku bunlarin bazi yanlis anlamalari gosterdiklerini dusunuyorum:

1) Ilk verdigin %c ile karakter okuyamama programinin, "cozumu olan bir sorun" oldugunu kabul etmiyorum. Yani ortada cozulmesi gereken bir sorun yok. Orada, belgeleri okumamis birisinin yazdigi bir program var. stdin, stdout, ve scanf'in nasil calistiklari belgelerinde yazili. Yani olay tamamen belgelere gore isliyor.

Eger programci bekledigi sonucu goremiyorsa, bekledigi sonucu verecek sekilde programlamadigi icindir.

2) 'char'in alfanumerik verilerle ilgisini yanlis bir kaynaktan okumus olmalisin? (C uzerine cok sayida yanlis kaynak ve hurafe vardir.) 'char', 0 ile N arasinda deger tutabilen bir tamsayi veri tUrudur. Gunumuzdeki hemen hemen butun ortamlarda N==255 olsa da, gecmiste bazi sistemlerde 255'ten farkli ust deger tutabilen 'char'lar da olmustur.

Yani, tanim geregi, 13 degeri 'char'in tutabilecegi bir degerdir.

3) Uc tane 13 degeri okuyamamanla ilgili olarak oncelikle "Enter'a basma" isinin C ile bir ilgisi olmadigini ogrenmen gerekiyor. C programlama dili, cok degisik ortamlarda calisabilsin diye klavye, ekran, vs. ile ilgilenmez, bunlarin kullanimlarini belirlemez.

C icin giris icin stdin akimi (klavye degil!) vardir, cikis icin de stdout akimi (ekran degil!) vardir. (stderr gibi baska akimlar da vardir; bunu bir kenara birakiyorum.)

'\n' cok ozel anlami olabilen bir karakter oldugu icin, programi calistirdigin ortamda da degisik anlamlari olabilir. Ornegin bir kabuk (shell), '\n' karakterini degisik bir anlamda kullaniyor olabilir. Bu noktada kabugun ne yaptigini da ogrenmen gerekiyor.

[Ara not: Ornegin bir programin standart girisini sonlandirma karakteri calisilan ortama gore degisir: Windows'da Ctrl-Z, Linux'ta Ctrl-D'dir.]

Deger olarak 13 okumak istiyorsan, scanf gibi belirli duzende giris okumaya elverisli bir islev yerine, dogrudan ikili degerler (baytlar?) okuyan islevler kullanmak isteyebilirsin: fread, read, vs.

Verdigin programda uc tane 13 degeri okuyamamanin nedeni, scanf'e bosluk karakterlerini goz ardi etmesini soylemis olmandir. Duzen dizgisinde kullanilan bosluklar (white space) "bu noktada gelen butun bosluklari gozardi et" anlamina gelir. '\n' karakterleri de bosluk kabul edildikleri icin, okunmamalari normaldir.

Bunun ustune, bir de %s kullanmissin. scanf'in belgelerine baktin mi? Orada anlatildigi gibi, %s bir satir basi karakterine kadar okur. Dolayisiyla %s ile '\n' karakterini okuyamazsin. Bu bir tanim geregidir, belgelerinde yaziyor.

Programini 3 tane '\n' okuyabilecek sekilde degistiriyorum:

[Ara not: Senin '\n' degil de '\r' okumak istedigini biliyorum. Bunun yanitini sonuncu maddede veriyorum.]


#include <stdio.h>

#define HARF_ICIN_YER 3

int main(int argc, char *argv[])
{
int sayi;
char harf[HARF_ICIN_YER];
int kacinci = 0;

printf("sayi girin:");
fflush(stdout);
scanf(" %d",&sayi);
printf("devam(E/H) ? ");
fflush(stdout);

while (kacinci < HARF_ICIN_YER) {
char karakter;
scanf("%c", &karakter);

printf("okudum: %d\n", karakter);

if (karakter == '\n') {
harf[kacinci] = karakter;
++kacinci;

printf("%d tane okudum\n", kacinci);
}
}

return 0;
}


Windows XP'de bir cmd penceresinde su ciktiyi verdi:


sayi girin:1
devam(E/H) ? okudum: 10
1 tane okudum

okudum: 10
2 tane okudum

okudum: 10
3 tane okudum


Yukarida da soyledigim gibi, o programi bir Windows XP kabugunda calistirmakla, Emacs'in bir kabugunda (shell komutu ile acilan) calistirmak arasinda davranis farklari yasadim. Cunku Enter tusunu her iki ortam degisik olarak yorumluyordu.

Yani, Enter tusu sonucunda programin girisine '\n' karakterinin verilip verilmemesi, ortamin keyfine gore bir durum. C dilinin guzelce tanim disinda biraktigi bir olay. Klavye diye bir kavram yok dilde! :)

4) Genel olarak, ne C'de ne de baska bir dilde, varsaymak yerine belgeleri okumamiz gerekiyor. Belli bir noktadan sonra kendi dusunce tarzimizi o dilin tarzina uydurur ve belgeleri okumadan idare edebiliriz ama o durumda da hatalar olur. (Bunun bir ornegi, std::string'in find islevinin aranan seyi bulamadigi zaman ne dOndUrdugunu varsaymaktir. Yanlis varsayildigini cok gordum.)

5) Tamponun silinmesi neden gereksin? %d'nin bir sayi bulamayana kadar okudugu belgesinde yazili. Sayi olmayan ilk karakter (bu durumda '\n') tabii ki giriste birakilacak ki, bir sonraki okuma ile gorebilelim.

Kabuklardaki < ve | karakterlerinin anlamini biliyor musun? Ornegin, bir programin standart girisini < karakteri ile baska bir dosyadan alsak; bir %d okuduk diye butun girisi silecek miyiz? Cok kullanissiz olurdu...

Daha once de dedigim gibi, bence bu beklentin klavyenin davranisinin C'nin tanimi icinde oldugunu varsaydigin icin oluyor. Ozellikle gunluk hayatta gordugumuz ornekler nedeniyle kuvvetli bir varsayim, ama dogru degil...

6) scanf'in "kullanici girdisi alalim diye" tanimlandigini nasil soyleyebiliyorsun? Bu nerede yazili? Herhalde C'yi ogrenirken ilk programlarda scanf kullandik diye boyle oluyor. Bence en kisa zamanda bir scanf belgesi okuman gerekiyor. scanf ailesindeki islevler bir giristen belirli bir duzende okumak icin kullanilirlar.

Ornegin sscanf'e bak... Bir dizgiden okur; kullanici olmadan...

7) scanf'i kullanarak CR okuyabilmen icin oncelikle programinin girisine o karakteri verecek bir ortam bulman gerekiyor. Programini calistirdigin ortam senin kodunun gordugu stdin'e o karakteri veriyorsa isin kolay: Benim progarmimdaki '\n'i '\r' ile degistir. Iste 3 tane '\r' okuyabiliyorsun... :)

Bunun bir yolu, o karakterleri bir dosyaya koymak ve programini girisini o dosyadan okuyacak sekilde baslatmaktir. Programin isminin 'deneme' ve o karakterlerin bulundugu dosyanin isminin 'giris_dosyasi' oldugunu varsayarsak, bir kabuk ortaminda su komutu calistir:

deneme < giris_dosyasi

Iste okudun! :)

[Ara not: Bir dosyaya '\r' gibi bir karakter girebilmenin birden fazla yolu olabilir. Ben Emacs'in hexl-modu'unu kullandim. Baska kolay bir yol, o dosyayi olusturan bir program yazmak olabilir.]

Baska bir yol, stdout'a istedigin cikisi yazdiran bir program yaz. (O cikista 3 tane '\r' karakteri olsun.) Sonra kendi programinin girisini o programin cikisina bagla. Ornegin soyle bir sey:

cikisina_CR_yazan_program | deneme

8) Evet, butun bu davranislar normal; cunku hersey tanimina uygun olarak gelisiyor.

Ali

Not: Sorulari yanitlamadan once varsayimlarin yanlisligini gostermek zaman aliyor. Daha az varsayimda bulunmak icin once iyi bir kaynaktan bilgi edinmeni oneririm.

mr_c
30/12/2006, 01:22
Öncelikle okuma tavsiyeleriniz hakkında teşekkür ederim. Bilgilerimi yanlış kaynaklardan almadım. Söylediğiniz şeylerin aksini yazan kitaplara da rastlamadım. Zaten bilgiyi sormadığımı notlarla belirttim. Fikrinizi soruyorum. C ye bu tarz yaklaşım sebebim nüansları kaçıran arkadaşların neden bunları kaçırdığı konusu.

Çözüm için ise verdiğim kodla doğrudan yapılamayacağını, belki çoğunun neden CR girişi yapmaya gerek duyayım ki, yada bu mekanizma nasıl çalışıyor ki gibi konuları tartışabilmek için bilmediğimden değil :)

Karakter alfa nümerik meselesinde biraz ayıp etmişsiniz. char sayıdır diyerek. Bilgisayarda sayı olmayan birşey mi var? Bir programcının dediği gibi benim bildiğim tek programlama dili var 100100011...

Görüldüğü kadarıyla doğrudan girildiği şekliyle olmasada hafif etrafından dolaşılarak bu dilin diğerlerinden farkını görüyor ve taktir ediyoruz.

Özenli cevaplarınız için teşekkür ederim. Eylemlerim devam edecek.

acehreli
30/12/2006, 02:43
char konusunda ayrica yanlis da soylemisim: deger araligi her zaman [0,255] degildir; cogu ortamda [-128,127]dir. :)

Ali

mr_c
30/12/2006, 02:51
Özenli cevaplarınız için teşekkür ederim. şimdi sözümü bir char ın range' i için geri mi alayım :)