C nin Stantart Dosya Fonksiyonlarının Uyguladığı Tamponlama Mekanizması

Benzer belgeler
ELN1002 BİLGİSAYAR PROGRAMLAMA 2

Temel Dosya İşlemleri. Kütük Organizasyonu 1

Eln 1002 Bilgisayar Programlama II

Eln 1001 Bilgisayar Programlama I

BLM-112 PROGRAMLAMA DİLLERİ II. Ders-8 Dosya İşlemleri-1. Yrd. Doç. Dr. Ümit ATİLA

Yrd. Doç. Dr. Caner ÖZCAN

C Dosyalama. Öğr. Gör. M. Ozan AKI. Rev 1.0

Temel Bilgisayar Programlama

Metin Dosyaları. Metin Dosyaları Dosya Açma ve Kapama Dosya Okuma ve Yazma Rastgele Erişim Standart Girdi/Çıktı Hata Kontrolü

DOSYALAR. Temel terimler Hafta. Dr. Fahri Vatansever

Dosyalar 1.1 Dosya Nedir?

mod ile açılacak olan dosyanın ne amaçla açılacağı belirlenir. Bunlar:

ALGORİTMA VE PROGRAMLAMA II

Yrd. Doç. Dr. Caner ÖZCAN

10. DOSYA GİRİŞ ÇIKIŞ FONKSİYONLARI

Temel Bilgisayar Programlama Final Sınavı Çalışma Notları

şeklinde tanımlanmıştır. O halde, dosyaları daha önceki bilgilerimizi kullanarak FILE *Dosya1, *Dosya2;

ELN1001 BİLGİSAYAR PROGRAMLAMA I

Sınav tarihi : Süre : 60 dak.

DOSYA İŞLEMLERİ Programlama dilleri hafta -

ALGORİTMA VE PROGRAMLAMA II

Hafta 12 Karakter Tutan Diziler

Dizgiler. C dilinde karakter m şeklinde tek tırnak içerisinde yazılan ifadelerdir. Bu karakterlerin her biri aslında bir tamsayı ile ifade edilir.

Strings(Karakter Dizisi)

ALGORİTMA VE PROGRAMLAMA II

12. Saat : Dosyalar I (Files)

Fonksiyonlar. C++ ve NESNEYE DAYALI PROGRAMLAMA 51. /* Fonksiyon: kup Bir tamsayının küpünü hesaplar */ long int kup(int x) {

Gereksiz Kodlar. burada if deyiminin else bölümüne gerek var mı? İfade doğruysa zaten fonksiyon geri dönüyor. Bu aşağıdakiyle tamamen eşdeğerdir:

Temel Giriş/Çıkış Fonksiyonları

BMB1002 Bilgisayar Programlamaya Giriş. Dosyalar. Prof. Dr. Necmettin Kaya

ELN1002 BİLGİSAYAR PROGRAMLAMA 2

Internet Programming II. Elbistan Meslek Yüksek Okulu Bahar Yarıyılı

Diziler (Arrays) Çok Boyutlu Diziler

Genel Programlama II

Sınav tarihi : Süre : 60 dak. a) strstr b) strchr c) strcat d) strcpy e) strlen. a) b) d) e) 0

BLM-111 PROGRAMLAMA DİLLERİ I. Ders-11 Karakter Diziler. Yrd. Doç. Dr. Ümit ATİLA

PROGRAMLAMA. Dosyalama İşlemleri. Yrd. Doç. Dr. Bülent Çobanoğlu. Sakarya Üniversitesi Mekatronik Mühendisliği. Yrd.Doç.Dr.

Programlama Dilleri 1. Ders 4: Diziler

C Konsol Giriş Çıkış Fonksiyonları

C Programlama Dilininin Basit Yapıları

Pointer Kavramı. Veri Yapıları

BLM-111 PROGRAMLAMA DİLLERİ I. Ders-8 Değişken Tipleri ve Temel Giriş/Çıkış İşlemleri

BÖLÜM 5: TEMEL GİRİŞ/ÇIKIŞ FONKSİYONLARI

BASİT C PROGRAMLARI Öğr.Gör.Dr. Mahmut YALÇIN

DİZİLER-KATARLAR ALGORİTMA VE PROGRAMLAMA II

AHMET YESEVİ ÜNİVERSİTESİ BİLİŞİM SİSTEMLERİ VE MÜHENDİSLİK FAKÜLTESİ BİLGİSAYAR MÜHENDİSLİĞİ LİSANS DÖNEM ÖDEVİ

ELN1002 BİLGİSAYAR PROGRAMLAMA 2

Yrd. Doç. Dr. Caner ÖZCAN

8. İŞARETCİLER (POINTERS)

Dr. Fatih AY Tel: fatihay@fatihay.net

PROGRAMLAMAYA GİRİŞ DERS 2

NESNEYE YÖNELİK PROGRAMLAMA

GENEL GĐRĐŞ-ÇIKIŞ FONKSĐYONLARI. ENF102 Jeoloji 1. #include <stdio.h> printf Fonksiyonu ÖRNEK. printf

BLM 112- Programlama Dilleri II. Hafta 5 İşaretçiler (Pointers)

UNIX Türevi Sistemlerde Temel Dosya Fonksiyonları: open, close, read, write ve lseek

Göstericiler (Pointers)

Yrd. Doç. Dr. Caner ÖZCAN

Dr. Fatih AY Tel: fatihay@fatihay.net

Yrd. Doç. Dr. Caner ÖZCAN

BLM 111 Algoritma ve Programlama I Güz 2018

Bölüm 2 - C ile Programlamaya Giriş

BLM-112 PROGRAMLAMA DİLLERİ II. Ders-3 İşaretçiler (Pointer) (Kısım-2)

BÖLÜM 6: KARŞILAŞTIRMALI KONTROL YAPILARI

Mifare Kart Yazıcı/Okuyucu Modül (MFM-200)

Programlama Dilleri. C Dili. Programlama Dilleri-ders08/ 1

NB Ekran Seri Port Üzerinden Veri Okuma/Yazma. Genel Bilgi Protokol Oluşturma Veri Okuma Veri Yazma

ALGORİTMA VE PROGRAMLAMA II

UNIX/Linux ve Windows Sistemlerinde Dosyaların ve Dizinlerin Silinmesi

Temel Bilgisayar Bilimleri Ders Notu #4-2. kısım

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

/ C Bilgisayar Programlama Final Sınavı Test Soruları. Adı soyadı :... Öğrenci no :... İmza :... Tarih, Süre : , 60 dak.

HSancak Nesne Tabanlı Programlama I Ders Notları

KOCAELİ ÜNİVERSİTESİ BİLGİSAYAR MÜHENDİSLİĞİ. BİLGİSAYAR LABORATUVARI II FİNAL SINAVI SORU ve CEVAPLARI(I. ogr)

NESNEYE YÖNELİK PROGRAMLAMA C++ a Giriş

Pascalda oluşturulacak dosyalar değişkenler gibi programın başında tanımlanır.

Fonksiyonlar (Altprogram)

BLM 111 ALGORİTMA VE PROGRAMLAMA I

C PROGRAMLAMA D İ L İ

Jval tipi. Genel veri yapılarını kullanacağımız zaman Jval den faydalanırız.önemli olanlar aşağıda mevcuttur:

Program Çözümleme. Aşağıdaki örneklerde printf() ve scanf() fonksiyonlarının işlevleri gösterilmektedir. Liste 1.1. Çözümleme:

ALGORİTMA VE PROGRAMLAMA I DERS NOTU#8

Hafta 7 C Programlama Diline Giriş ve C Derleyicisi

C Programlama printf() Fonksiyonu

String ve Karakter Dizileri. Yrd. Doç. Dr. Fehim KÖYLÜ Erciyes Üniversitesi Bilgisayar Mühendisliği Bölümü

ALGORİTMA VE PROGRAMLAMA I

BİLGİSAYAR TEMELLERİ VE PROGRAMLAMAYA GİRİŞ

Temel Giriş/Çıkış Fonksiyonları (Devam) Örnek :

Bil101 Bilgisayar Yazılımı I. M. Erdem ÇORAPÇIOĞLU Bilgisayar Yüksek Mühendisi

Ders 4: Temel Giriş/Çıkış Fonksiyonları

1 PROGRAMLAMAYA GİRİŞ

UNIX/Linux ve Windows Sistemlerinde stdin, stdout ve stderr Dosyaları

Fen ve Mühendislik Uygulamalarında MATLAB

Örnek: İki fonksiyondan oluşan bir program. Fonksiyon Tanımı

PROGRAMLAMAYA GİRİŞ FONKSİYONLAR

ALFASAYISAL BİLGİLER KARAKTER DİZİLERİ (STRING)

İstanbul Teknik Üniversitesi IEEE Öğrenci Kolu DİZİLER

DÖNGÜ DEYİMLERİ (while, do while, for)

Diziler. Yrd.Doç.Dr.Bülent ÇOBANOĞLU

ALGORİTMA VE PROGRAMLAMA I

Sınav tarihi : Süre : 60 dak. a) ABCDE b) BCDE c) ABCD d) kod hatalı e) BCD

Transkript:

C nin Stantart Dosya Fonksiyonlarının Uyguladığı Tamponlama Mekanizması Kaan Aslan 16 Temmuz 2003 Standart C fonksiyonlarını kullanmadan bir dosyanın her byte ı üzerinde sırasıyla işlem yapmak isteyelim. Herhalde ilk akla gelecek yöntem doğrudan işletim sisteminin sistem fonksiyonlarını çağırmak olacaktır. Örneğin UNIX/Linux sistemlerinde dosyayı read fonksiyonuyla (Windows sistemlerinde ReadFile fonksiyonuyla) byte byte aşağıdaki gibi okuyabiliriz: int fd; ssize_t result; unsigned char ch; if ((fd = open("test", O_RDONLY)) < 0) { perror("open"); exit(exit_failure); while ((result = read(fd, &ch, 1)) > 0) { /* Okunan byte işleniyor */ if (result < 0) { perror("read"); exit(exit_failure); close(fd); Dosyayı bu biçimde read fonksiyonuyla byte byte okumak göreli olarak yavaş bir işlemdir. Çünkü her byte'ın okunması için çağrılan read fonksiyonu prosesi çekirdek moduna geçirerek işlemini yapar. Evet, read fonksiyonu prosesi çekirdek moduna geçirdikten sonra okunacak byte'ı işletim sisteminin aşağı seviyeli disk önbellek sisteminde (buffer cache) arayacak ve belki de bu nedenle çok az bir bir disk işlemi yapılacaktır. Fakat ne olursa olsun çekirdek moduna geçiş ve önbellek sistemindeki sorgulama zaman alıcı işlemlerdir. Pekiyi bunun için nasıl bir yol izleyebiliriz? İlk akla gelecek yöntem read fonksiyonuyla her defasında bir byte okumak yerine bir hamlede çok byte okuyup bunları bir diziye yerleştirmek ve daha sonra byte'ları bu diziden almak olabilir. Böylece read fonksiyonunu daha az çağırmayı başarabiliriz. Örneğin read fonksiyonuyla her defasında 512 byte'ı bir diziye okuyup byte'ları teker teker bu diziden alabiliriz. Böylece read fonksiyonunu 512 kez daha az çağırmış oluruz. İşte burada sözünü ettiğimiz tamponlama işlemi zaten standart C fonksiyonları tarafından yapılmaktadır. C'nin standart dosya fonksiyonları sistem fonksiyonlarını daha az çağırmak için prosesin adres alanı içerisinde tamponlama uygular. Bu tamponlama sistemine göre, 1

dosyadan okuma yapmak istediğimizde standart C fonksiyonları önce bu isteği tampondan karşılamaya çalışır; eğer istek tampondan karşılanamazsa işletim sisteminin sistem fonksiyonlarını (POSIX sistemlerinde read fonksiyonunu, Windows sistemlerinde ReadFile fonksiyonunu) çağırarak tamponu yeni bir grup bilgiyle doldurur. Tüm standart C fonksiyonları önce tampona bakacak biçimde tasarlanmıştır. Örneğin POSIX sistemlerinde bir dosyayı fopen fonksiyonuyla açıp fgetc fonksiyonuyla byte byte okuduğumuzu düşünelim: FILE *f; int ch; if ((f = fopen("test", "r")) == NULL) { fprintf(stderr, "Cannot open file!..\n"); exit(exit_failure); while ((ch = fgetc(f))!= EOF) { /* Okunan byte işleniyor */ fclose(f); Dosyadan ilk byte'ı okuduğumuzda fgetc fonksiyonu tamponun boş olduğunu görecek ve read fonksiyonunu çağırarak tamponu dolduracaktır. Artık diğer fgetc çağırmalarında talep edilen bilgiler tampondan karşılanabilecektir. Konuya devam etmeden önce önbellek (cache) ve tampon (buffer) kavramları üzerinde biraz durmak istiyoruz. Önbellek ve tampon birbirlerine benzer ve bazen de birbirlerinin yerine kullanılabilen iki kavramdır. Tampon, bilgilerin geçici süre saklandığı bellek bölgelerini belirtir. Tampon kavramında bilgilerin geçici olarak bekletileceği bellek bölgesi üzerine vurgu yapılmaktadır. Önbellek terimi ise etimolojik olarak donanımsal kökenlidir. Önbellek sistemleri yavaş bellekteki bilgilerin bir kısmının hızlı bellekte tutulduğu ve böylece yavaş belleğe erişimin azaltıldığı sistemlerdir. Her önbellek sistemi de bir tampon içermektedir fakat her tampon bir önbellek sistemini gerçekleştirmek amacıyla kullanılıyor olmak zorunda değilidir. Standart C fonksiyonlarının kullandığı sistem daha çok bir önbellek sistemini çağrıştırıyor olsa da tampon ve tamponlama terimlerinin bu bağlamda daha yaygın olarak kullanıldığını görüyoruz. Bu nedenle biz de standart C fonksiyonlarının uyguladığı bu sisteme tamponlama (buffering) diyeceğiz. Modern sistemlerde standart C fonksiyonlarıyla çalıştığımız zaman iki düzey bir tamponlama sistemi içerisinde bulunuyor oluruz: Standart C fonksiyonlarının proses düzeyinde oluşturmuş olduğu tamponlama sistemi ve çekirdek tarafından oluşturulan aşağı seviyeli disk tamponlama sistemi (buffer cache). Stdio tamponlamasının amacı sistem fonksiyonlarını daha az çağırmak, çekirdek tamponlamasının (buffer cache) amacı ise disk erişimini azaltmaktır. Bu iki düzey tamponlamayı şekilsel olarak şöyle gösterebiliriz: 2

Standart C fonksiyonlarının uyguladığı tamponlama sistemi bir grup bilginin önce tampona çekilmesi ve oradan hedefe aktarılması fikrine dayanan basit bir sistemdir. Stdio tamponu hem okunabilen hem de yazılabilen (read/write) bir sistemidir. Yani okuma işlemi de yazma işlemi de tampon kullanılarak yapılmaktadır. Aslında C standartlarında stdio tampon sistemi yalnızca genel hatlarıyla açıklanmıştır ve ayrıntılara girilmemiştir. Fakat tipik olarak tek bloklu (single cache line) bir tampon sistemi kullanılır. Bu sistemde okuma yapıldığında eğer okunacak bilgi tamponda yoksa tampon ardışıl byte larla doldurulur ve sonraki işlemler tampondan karşılanır. Yazma sırasında da bilgi önce tampona yazılır. Tampona daha önce yazılmış bilgiler başka bilgilerin tampona çekilmesiyle, dosya göstericisinin konumlandırılmasıyla ya da açıkça fflush fonksiyonunun çağrılmasıyla dosyaya aktarılmaktadır. fopen fonksiyonuyla Açılan her dosyanın ayrı bir stdio tamponu vardır. Kullanılan tamponun varsayılan uzunluğu <stdio.h> doyasında bildirilmiş olan BUFSIZ sembolik sabitinin belirttiği değer kadardır. (Pek çok derleyicide BUFSIZ değeri 512 biçiminde define edilmiştir.) Şüphesiz <stdio.h> içerisindeki BUFSIZ sembolik sabitininin değeri ile oynayarak dosya tamponlarının varsayılan uzunluklarını değiştiremeyiz. BUFSIZ sembolik sabiti varsayılan büyüklüğün programcı tarafından bilinmesi için define edilmiştir. fgetc fonksiyonunu kullanarak bir dosyayı byte byte okuduğumuzu düşünelim. Tamponlama mekanizması kullanılıyor olsa bile böylesi bir işlemde fgetc fonksiyonunun her byte için yeniden çağrılması göreli bir zaman kaybına yol açabilmektedir. Çünkü ne de olsa fonksiyon çağırmanın da bir zamansal maliyeti vardır. Gerçi disk işlemlerinin söz konusu olduğu pek çok durumda böyle küçük kayıpların önemsiz olduğunu biliyorsunuz. Fakat yine de bazı kritik uygulamalar için bu zaman kaybının engellenmesi amacıyla standart C kütüphanesinde getc isimli bir fonksiyon da bulundurulmuştur. getc fonksiyonu fgetc fonksiyonunun makro biçiminde gerçekleştirilebilen uyarlamasıdır. (C standartlarında getc fonksiyonunun makro biçiminde yazılması zorunlu tutulmamıştır. Fakat pek çok derleyici bu fonksiyonu tipik olarak makro biçiminde bildirmektedir.) getc bir makro biçiminde oluşturulmuşsa, fonksiyon çağırmasından kaynaklanan göreli zaman kaybına yol açmaz. Benzer biçimde fputc fonksiyonunun da putc isimli makrolu bir uyarlaması vardır. fopen fonksiyonundan elde edilen dosya bilgi göstericisinin FILE isimli bir yapı türünden olduğunu biliyorsunuz. İşte FILE yapısının içerisinde tamponlama mekanizmasını yönetebilmek için gerekli elemanlar bulunmaktadır. fopen fonksiyonu FILE türünden yapı nesnesini tahsis eder, işletim sisteminin sistem fonksiyonlarıyla 3

dosyayı açar, tahsis ettiği FILE türünden yapı nesnesinin elemanlarına ilkdeğerlerini vererek bu nesnenin adresiyle geri döner. Örneğin bir C derleyicisindeki FILE yapısı şöyledir: typedef struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; FILE; Yapının _file elemanı sistem fonksiyonlarını çağırmak için gereken dosya betimleyicisidir. Tamponun adresi _base elemanı ile, aktif noktası da _ptr elemanı ile tutulmaktadır. Yapının _bufsize elemanı tamponun uzunluğunu, _cnt elemanı ise tampondaki byte sayısını belirtiyor (tamponun hepsi dosya içeriği ile dolu olmak zorunda değildir). Söz konusu kütüphanede fgetc fonksiyonu aşağıdaki gibi yazılmıştır: int fgetc (FILE* F) { return (-- F->_cnt >= 0)? (int) * F->_ptr++ : _filbuf ( F); Fonksiyonun _ptr göstericisinin gösterdiği yerdeki byte değeri ile geri döndüğünü görüyorsunuz. Eğer tamponun sonuna gelinmişse _fillbuf fonksiyonu çağrılarak tampon tazelemesi yapılmıştır. Yukarıda standart C fonksiyonlarının tampon mekanizmasını kabaca açıkladık. Şimdi bazı ayrıntılara gireceğiz. C'nin standart dosya fonksiyonları üç tamponlama modunda kullanılabilmektedir: 1. Tam tamponlamalı (full buffered) mod 2. Satır tamponlamalı (line buffered) mod 3. Sıfır tamponlamalı (unbuffered) mod Tam tamponlamalı modda tampon tam kapasiteyle kullanılır. Yani bilgi mümkün olduğu kadar tamponda bekletilmektedir. Dosyadan okuma yapılmak istendiğinde önce tampona başvurulur. Bilgi tamponda varsa oradan alınır. Eğer bilgi tamponda yoksa talep edilen bilginin bulunduğu kısım dosyadan okunarak tampona aktarılır. Tampon sistemi yukarıda da belirttiğimiz gibi hem okunabilen hem de yazılabilen bir sistemdir. Yani yazma işleminde de tampon kullanılmaktadır. Tam tamponlamalı durumda tampon ile dosya arasındaki aktarımlar tipik olarak şu durumlarda yapılmaktadır: 1. Yeni yapılan okuma işleminin tampondan karşılanamaması durumunda (cache miss). Böylesi bir durumda dosyaya başvurularak tampon doldurulur. Tabi tampona 4

daha önce yazma yapılmışsa tampondaki değişmiş bilgi de yeniden dosyaya aktarılacaktır. 2. Dosya göstericisi konumlandırıldığında. Dosya göstericisinin konumlandırılması sırasında konumlandırılan yerdeki bilgiler tampona çekilmektedir. 3. fflush işlemi sırasında. Dosya yazma modunda açılmışsa fflush fonksiyonu tampondaki bilgileri doğrudan dosyaya aktarır. Satır tamponlamalı modda tampona yalnızca bir satırlık bilgi alınır. Örneğin bu modda fgetc fonksiyonu ile bir byte okumak isteyelim. fgetc fonksiyonu tampon boşsa tamponu bir satırlık bilgiyle, yani dosya göstericisinin gösterdiği yerden itibaren '\n' karakterini görene kadar olan karakterlerle ( \n karakteri de dahil olmak üzere) doldurur. Benzer biçimde bu modda yazma durumu söz konusu olduğunda dosyaya aktarım için tamponun dolması beklenmez. Aktarım '\n' karakteri görüldüğünde (bu karakter dahil olmak üzere) yapılır. Örneğin, dosyanın satır tamponlamalı olarak açıldığını varsayalım ve dosyaya aşağıdaki gibi bir yazma yapmış olalım: fprintf(f, "Bu birinci denemedir"); bu bilgi önce tampona yazılacaktır ve henüz akratım yapılmayacaktır. Şimdi de aynı dosyaya aşağıdaki gibi bir yazma yaptığımızı düşünelim: fprintf(f, "Bu ikinci denemedir\n"); Bu karakterler de önce tampona yazılacak fakat '\n' karakteri görüldüğünden öncekilerle birlikte dosyaya aktarım yapılacaktır. Görüldüğü gibi satır tamponlamasında tamponda yalnızca bir satırlık bilgi tutulmaktadır. Sıfır tamponlamalı modda tampon hiç kullanılmaz. Bu durumda standart C fonksiyonları doğrudan sistem fonksiyonlarını çağırarak aktarımı yapar. Örneğin dosyanın sıfır tamponlamalı modda açıldığını varsayalım. UNIX türevi bir sistemde dosyadan fgetc fonksiyonuyla bir byte okuduğumuzda fgetc doğrudan read fonksiyonu çağırarak aktarımı yapacaktır. Bir dosyanın tamponlama modu ve tampon olarak kullanılacak bölge dosya açıldıktan sonra setbuf ve setvbuf fonksiyonlarıyla değiştirilebilir. Bu fonksiyonlar dosya fopen fonksiyonu ile açıldıktan sonra fakat dosya üzerinde henüz bir işlem yapılmadan önce çağrılmalıdır. setvbuf fonksiyonu işlevsel olarak setbuf fonksiyonunu kapsamaktadır. Bu nedenle önce setvbuf fonksiyonunu inceleyeceğiz: #include <stdio.h> int setvbuf(file *stream, char *buf, int mode, size_t size); Fonksiyonun birinci parametresi açılan dosyaya ilişkin dosya bilgi göstericisini, ikinci parametresi ilgili dosya tarafından kullanılacak tampon alanı belirtmektedir. Yani setvbuf fonksiyonu ile biz kendi tahsis ettiğimiz bir alanın tampon olarak kullanılmasını sağlayabiliriz. Üçüncü parametre tamponlama modunu belirtmektedir. Bu parametre _IOFBF, _IOLBF ya da _IONBF değerlerinden biri olarak girilebilir. 5

_IOFBF tam tamponlama, _IOLBF satır tamponlaması, _IONBF ise sıfır tamponlama anlamına gelmektedir. Fonksiyonun son parametresi ise ikinci parametre ile verilen tampon alanın uzunluğunu belirtmektedir. Eğer fonksiyonun üçüncü parametresi (yani tamponlama modu) _IONBF biçiminde girilirse ikinci parametredeki tampon alanın adresi ve son parametredeki tampon alanının uzunluğu dikkate alınmaz. Eğer tampon alanın adresi NULL adres olarak girilirse bu durumda tampon alan setvbuf fonksiyonunun kendisi tarafından tahsis edilir. Fonksiyon başarı durumunda sıfır değerine, başarısızlık durumunda sıfır dışı herhangi bir değer geri döner. Örneğin: FILE *f; if ((f = fopen("test.txt", "r")) == NULL) { fprintf(stderr, "Cannot open file!..\n"); exit(exit_failure); setvbuf(f, NULL, _IOLBF, BUFSIZ * 2); Burada dosya açılarak satır tamponlamalı moda geçirilmiştir. Fonksiyonun ikinci parametresi olan tampon adresi NULL geçildiğine göre BUFSIZ * 2 uzunluğundaki tamponun tahsisatı fonksiyonun kendisi tarafından yapılacaktır. Dosyayı aşağıdaki gibi sıfır tamponlamalı moda geçirebilirdik: setvbuf(f, NULL, _IONBF, 0); Eğer tampon alanı programcı tahsis edecekse, tahsis edilen alanın dosya kapatılana kadar yaşıyor durumda olması gerekir. Tipik olarak tampon alan dinamik bellek fonksiyonlarıyla tahsis edilebilir ya da static bir alan tampon olarak kullanılabilir. Örneğin: FILE *f; char *pbuf; if ((f = fopen("test", "r")) == NULL) { fprintf(stderr, "Cannot pen file!..\n"); exit(exit_failure); if ((pbuf = (char *) malloc(bufsiz * 2)) == NULL) { fprintf(stderr, "Cannot allocate memory!..\n"); exit(exit_failure); setvbuf(f, pbuf, _IOFBF, 2 * BUFSIZ);... fclose(f); free(pbuf); setvbuf fonksiyonu başarı durumunda 0 değerine başarısızlık durumunda -1 değerine geri döner. Argümanlar doğru girilse bile fonksiyon başarılı olmak zorunda değildir. 6

Yani tamponlama modunun değiştirilmesine derleyiciniz izin verebilir ya da vermeyebilir. setbuf fonksiyonu setvbuf fonksiyonunun daha az yeteneki bir biçimidir: #include <stdio.h> void setbuf(file *stream, char *buf); Fonksiyonun birinci parametresi ilgili dosyanın dosya bilgi göstericisini, ikinci parametresi ise tampon olarak kullanılacak alanın adresini alır. setbuf fonksiyonu dosyaya ilişkin tampon alanını değiştirmek için ya da dosyayı sıfır tamponlamalı moda geçirmek için kullanılmaktadır. Fonksiyonun ikinci parametresi NULL adres girilirse dosya sıfır tamponlamalı moda geçirilmiş olur. Dosyaların tamponlama modlarının setvbuf ve setbuf fonksiyonlarıyla değiştirilebileceğini gördük. Pekiyi bir dosya ilk açıldığındaki varsayılan tamponlama modu nedir? C standartlarında stdin, stdout ve stderr dışındaki dosyaların varsayılan tamponlama modları hakkında bir belirlemede bulunulmamıştır. Bu durumda bir dosyayı fopen fonksiyonuyla açtığımızda derleyiciler o dosyanın varsayılan tamponlama modunu herhangi bir biçimde belirleyebilirler. Şüphesiz en faydalı durum tam tamponlama olduğuna göre, eğer özel bir durum söz konusu değilse derleyicilerin bu belirlemeyi tam tamponlama olarak yapacağını söyleyebiliriz. Fakat standartlarda stdin, stdout ve stderr dosyalarının başlangıçtaki tamponlama modları için bazı belirlemelerde bulunulmuştur. Bunları iki maddede özetleyebiliriz: 1. stderr dosyası başlangıçta hiçbir zaman tam tamponlamalı modda olamaz. Satır tamponlamalı ya da sıfır tamponlamalı modda olabilir. 2. stdin ve stdout dosyaları başlangıçta karşılıklı etkileşimli bir aygıta yönlendirilmişse tam tamponlamalı modda, karşılıklı etkileşimli bir aygıta yönlendirilmemişse satır ya da sıfır tamponlamalı modda olmak zorundadır. (Klavye ve ekran karşılıklı etkileşimli aygıtlardır.) Bu anlatımlardan çıkan sonuçlar şunlardır: 1. stderr dosyası nereye yönlendirilmiş olursa olsun başlangıçta hiçbir zaman tam tamponlamalı modda olamaz. 2. stdout dosyası ekrana yönlendirilmişse başlangıçta tam tamponlamalı olamaz fakat satır ya da sıfır tamponlamalı olabilir. Benzer biçimde stdin dosyası da başlangıçta klavyeye yönlendirilmişse tam tamponlamalı olamaz fakat satır ya da sıfır tamponlamalı olabilir. 3. stdin ve stdout dosyaları program çalışmaya başladığında normal bir disk dosyasına yönlendirilmişse tam tamponlamalı modda olmak zorundadır. Burada stdin dosyası hakkında özel birkaç şey söylemekte yarar görüyoruz. C derleyicilerinin hemen hepsinde stdin dosyası başka bir aygıta yönlendirilmemişse satır tamponlamalıdır ve bu dosya sıfır tamponlamalı moda geçirilemez. Bunun 7

nedeni işletim sistemlerinin ve kabuk programlarının tasarımından kaynaklanmaktadır. Bu nedenle her ne kadar standartlarda işin başında stdin dosyasının sıfır tamponlamalı olabileceği belirtilmişse de uygulamada karşılaşılan tipik durum bu dosyanın satır tamponlamalı olmasıdır. C de prototipleri <stdio.h> içerisinde olan ve başı f harfiyle başlamayan scanf, printf, gets, puts gibi fonksiyonlar da aslında birer dosya fonksiyonudur. Bu fonksiyonlar varsayılan bir biçimde stdin ve stdout dosyalarını kullanırlar. Örneğin scanf fonksiyonu fscanf fonksiyonunun stdin dosyasından okuyan biçimi, printf fonksiyonu da fprintf fonksiyonunun stdout dosyasına yazan biçimidir. Yani: printf(...); scanf(...); çağırmaları ile: fprintf(stdout,...); fscanf(stdin,...); çağırmaları tamamen aynı işleve sahiptir. stdin ve stdout dosyaları da tıpkı diğer dosyalarda olduğu gibi tamponlama mekanizmasını kullanmaktadır. Oysa pek çok C programcısının bunu göz ardı ettiğini görüyoruz. stdin dosyasından okuma yapan getchar, scanf, gets gibi fonksiyonların aynı akımdan okuma yaptıkları için- aynı tamponu kullandıklarına dikkat ediniz. stdin dosyasından okuma yapan birkaç fonksiyon hakkında yapacağımız açıklamalar faydalı olabilir. getchar fonksiyonu stdin dosyasından 1 byte okumakta kullanılır. Prototipi şöyledir: int getchar(void); Fonksiyon başarı durumunda okunan değere, başarısızlık durumunda <stdio.h> içerisinde bildirilmiş olan EOF değerine geri döner. Başarısızlığın en önemli nedeni dosya sonuna gelinmiş olmasıdır. Fakat genel bir giriş/çıkış hatası da başarısızlığa yol açabilir. Bu durum feof ya da ferror fonksiyonuyla sorgulanabilir. C standartlarında EOF sembolik sabitinin hangi değerle tanımlanacağı konusunda kesin bir belirlemede bulunulmamıştır. Fakat C derleyicilerinin hemen hepsinde EOF sembolik sabitinin -1 olarak tanımlandığını görmekteyiz: #define EOF (-1) getchar fonksiyonunun geri dönüş değerinin int türden olduğuna dikkat ediniz. Fonksiyon başarı durumunda yüksek anlamlı byte ları sıfır olan düşük anlamlı byte ı okunan karakter olan bir değerle geri döner. Fonksiyonun neden char türüyle değil de int türüyle geri döndüğünü merak edebilirsiniz. Eğer fonksiyonun geri dönüş değeri char türden olsaydı fonksiyonun 0xFF numaralı byte mı okuduğu yoksa başarısız mı olduğu anlaşılamazdı. 8

gets fonksiyonu \n karakterini görene kadar ( \n karakteri de dahil olmak üzere) stdin dosyasından okuma yapar; \n karakteri yerine \0 karakterini diziye yerleştirir. gets fonksiyonunun prototipine dikkat ediniz: char *gets(char *s); Fonksiyon normal olarak parametresiyle aldığı adresin aynısına, hiç bir karakter okuyamadan EOF ile karşılaşırsa ya da bir giriş/çıkış hatası oluşursa NULL adresle geri dönmektedir. Aşağıdaki kod parçasını inceleyiniz: int ch1, ch2; char s[10];... ch1 = getchar(); putchar(ch); ch2 = getchar(); putchar(ch); gets(s); printf("%c\n%c\n%s\n", ch1, ch2, s); Burada birinci getchar çağrısı stdin tamponu boş olduğu için ve satır tamponlamalı mod söz konusu olduğu için ENTER tuşuna basılana kadar tüm bir satırı kullanıcıdan ister. Şimdi kullanıcının ilk getchar çağrısı için şunları girdiğini varsayalım: ankara<enter> Burada tüm bu karkterler tampona aktarılır. Girişin sonundaki ENTER karakteri de tampona \n karakteri olarak yerleştirilir. Tamponun görünümü şöyle olacaktır: Böylece ilk getchar fonksiyonu a karakterini alır. Tampon göstericisi bir ilerletilir. Akış ikinci getchar fonksiyonuna geldiğinde tamponun durumu şöyle olacaktır: ikinci getchar fonksiyonu tampon dolu olduğu için klavyeden giriş istemez. Tampondan n karakterini alarak onunla geri döner. Akış gets fonksiyonuna geldiğinde tamponun durumu şöyle olacaktır: 9

gets fonksiyonu da \n karakterini görene kadar ( \n karakteri de dahil) tampondan karakterleri okuyacak ve diziye yerleştirecektir. Bu durumda ekrana şunlar çıkacaktır: a n kara gets fonksiyonunun tasarımının hatalı olduğu artık herkes tarafından kabul edilmektedir. Çünkü fonksiyona argüman olarak biz ne kadar büyük bir dizi geçirirsek geçirelim ondan daha uzun bir satır girişi söz konusu olabilir. (Gerçi işletim sistemleri konsol girişlerini belli bir karakterde sınırlayabiliyor. Fakat bunun belirlenmesi de standart bir biçimde mümkün değildir.) Yani gets fonksiyonunu kullanırken dizi taşmasını engellemenin kesin bir yolu yoktur. Halbuki fonksiyona bir güvenlik parametresi eklenerek sorun giderilebilirdi. gets fonksiyonunun güvenlik parametresi alan düzeltilmiş bir biçimi şöyle yazılabilir: char *mygets(char *s, size_t size) { unsigned i; int ch; if (size == 0) return NULL; for (i = 0; i < size - 1; ++i) { ch = getchar(); if (ch == '\n') break; if (ch == EOF) { if (i == 0) return NULL; break; s[i] = ch; s[i] = '\0'; return s; 10

Bazı programcılar gets yerine fgets fonksiyonunu kullanmayı tercih ediyorlar. Ancak fgets fonksiyonunun \n karakterini de diziye yerleştirdiğini unutmayınız. gets yerine fgets kullanımının tipik biçimi şöyledir: char buf[size], *str; fgets(buf, SIZE, stdin); if ((str = strchr(buf, '\n'))!= NULL) *str = '\0'; Burada eğer satırdaki karakter sayısı SIZE 1 değerinden küçükse \n karakteri dizinin sonuna yerleştirilir. strchr fonksiyonuyla bu durum sorgulanmış ve eğer dizinin sonunda \n karakteri varsa bu karakter silinmiştir. Pekiyi stdin bir dosya olduğuna göre bu dosyanın bir sonu var mıdır? Bu soruyu şöyle yanıtlayabiliriz: Eğer stdin bir disk dosyasına yönlendirilmişse evet; fakat varsayılan durumda olduğu gibi terminale yönlendirilmişse hayır. Çünkü terminal gerçek bir dosya değildir. Biz getchar fonksiyonu ile okuma yapa yapa terminal dosyasının sonuna gelemeyiz. Fakat yine de klavyenin bir dosya gibi davranabilmesi için dosya sonu etkisi yaratan özel bir tuş birleşimi düşünülmüştür. Klavyeden bu özel bir tuş birleşimine basılırsa bu durum sanki stdin için dosya sonuna gelme (EOF) etkisi yaratır. Dosya sonuna gelme etkisi yaratan tuş birleşimleri Windows ta Ctrl Z, UNIX/Linux sistemlerinde genellikle Ctrl D dir. Örneğin: int ch; while ((ch = getchar())!= EOF) putchar(ch); Burada dosya sonuna gelinene kadar döngü içerisinde okuma yapıldığını görüyorsunuz. İlk getchar bizden bir satırlık bilgiyi isteyecek ve onun yalnızca ilk karakteriyle geri dönecektir. Sonraki getchar çağrıları tampondaki diğer karakterleri okuyacaktır. Tampondaki tüm karakterler okunduktan sonra sıradaki getchar yeni bir satırı bizden isteyecektir. Döngüden çıkmak için Windows sistemlerinde Ctrl Z, UNIX/Linux sistemlerinde Ctrl D tuşlarına basmak gerekir. Ancak bir noktaya dikkatinizi çekmek istiyoruz. Burada sözünü ettiğimiz özel tuş birleşimleri stdin dosyasını kapatmaz, yalnızca dosya sonuna gelinmiş gibi bir etki yaratır. Yani biz bu özel tuş birleşimlerine bastıktan sonra stdin dosyasından okuma yapmaya devam edebiliriz. Eğer stdin dosyasından okuma yapan bir fonksiyonun kullanıcıdan yeniden giriş almasını istiyorsanız stdin tamponunu boşaltmalısınız. Maalesef bunun için standart bir C fonksiyonu bulundurulmamıştır. [1] stdin tamponunun boşaltılması için getchar fonksiyonu ile \n karakteri ya da EOF karakteri görülene kadar okuma yapılabilir: while (getchar()!= '\n') ; 11

Bu döngüden EOF görüldüğünde (yani klavyeden özel tuş birleşimine basıldığında) çıkılamaz. Eğer döngüden EOF görüldüğünde de çıkılmasını istiyorsanız aşağıdaki kalıbı kullanabilirsiniz: int ch; while ((ch = getchar())!= '\n' && ch!= EOF) ; Örneğin: int ch1, ch2; ch1 = getchar(); while (getchar()!= '\n') ; ch2 = getchar(); burada hem ch1 hem de ch2 için yeniden giriş istenecektir. scanf fonksiyonu stdin dosyasından karakterleri teker teker okur, girişin başındaki boşluk karakterlerini atar, format karakterine uygun olmayan ilk karakteri gördüğünde onu tampona geri bırakarak işlemini sonlandırır. Prototipi şöyledir: int scanf(const char *format,...); scanf normal olarak yerleştirme yaptığı parça sayısına geri dönmektedir. Eğer hiçbir yerleştirme yapamadan dosya sonuna gelinirse ya da giriş/çıkış hatası oluşursa fonksiyon EOF değerine geri döner. Örneğin: int a, b, result; char s[50]; result = scanf("%d%d", &a, &b); gets(s); Aşağıdaki gibi bir giriş yapılmış olsun: 100ankara<ENTER> scanf fonksiyonu 1, 0, 0 karakterlerinden sonra a karakterini okuduğunda bunun formata uygun olmadığını görür; bu karakteri ungetc fonksiyonuyla tampona geri bırakarak 1 değeri ile geri döner. Dolayısıyla daha sonra çağrılan gets fonksiyonu tampondaki ankara yazısını okuyacaktır. Aşağıdaki gibi okumalarda tamponu boşaltmayı unutmayınız: 12

int no; char name[50]; scanf("%d", &no); while (getchar()!= \n ) ; gets(name); Daha önce de belirttiğimiz gibi stdout dosyası için de tampon kullanılmaktadır. Yazılanlar önce tampona yazılır ve daha sonra tampondan dosyaya aktarılır. Aşağıdaki örneği inceleyiniz: int val; printf("bir sayi giriniz:"); scanf("%d", &val); Eğer stdout dosyasının varsayılan tamponlama modu satır tamponlamalı (line buffered) ise bu durumda akış scanf çağrısına geldiğinde printf ile stdout dosyasına yazdırılanlar ekrana çıkmaz. Çünkü satır tamponlamalı modda \n karakteri görüldüğünde aktarım yapılmaktadır. Tabi stdout dosyasının varsayılan tamponlama modu sıfır tamponlamalı (unbuffered) ise yazılanlar hemen ekranda görünecektir. stdout dosyasının tamponlama modunun bazı derleyicilerde satır tamponlamalı bazılarında ise sıfır tamponlamalı olabileceğini anımsatalım. Eğer kod aşağıdaki gibi yazılsaydı hangi tamponlama modu söz konusu olursa olsun bir sorun çıkmazdı: int val; printf("bir sayi giriniz:\n"); scanf("%d", &val); Fakat burada da imlecin aşağı satırın başına geçtiğini görüyorsunuz. Eğer bunu istemiyorsanız stdout üzerinde fflush işlemi uygulamalısınız: int val; printf("bir sayi giriniz:"); fflush(stdout); scanf("%d", &val); Aynı etkiyi yaratabilmek için programın başında stdout dosyasını setvbuf fonksiyonuyla sıfır tamponlamalı moda de geçirebilirdiniz. [1] Bazı C derleyicilerinde sistemlerde fflush(stdin) çağırması ile stdin tamponu boşaltılabilmektedir. Ancak C standartlarına göre stdin dosyası yalnızca okunabilir modda açılmış durumdadır ve yazma modunda açılmayan dosyalara fflush işlemi uygulanamaz. Dolayısıyla fflush(stdin) işlemi C standartlarına göre tanımsız davranışa yol açmaktadır. 13

Kaynaklar Aslan, K. (2001). Sistem Programlama ve İleri C Uygulamaları Kurs Notları. İstanbul: C ve Sistem Programcıları Derneği. International Organization for Standardization / International Electrotechnical Commission. (1990). International Standard ISO/IEC 9899: Programming languages - C. New York: American National Standards Institute. Plauger, P. J. (1992). The Standard C Library. New Jersey: Prentice Hall. Stevens, R. (1993). Advanced Programming in the UNIX(R) Environment. Addison- Wesley Professional. 14