> C standart dosya fonksiyonlarındaki tamponlama mekanizmaları: Üç farklı tamponlama mekanizması kullanılmaktadır. Bunlar "Full Buffering(Tam Tamponlama)", "Line Buffering(Satır Tamponlama)" ve "No Buffering(Sıfır Tamponlama)" şeklindedir. Bu üç tamponlama yöntemi de açılan her dosya için ayarlanabilmektedir. Pekiyi nedir bu tamponlama mekanizmaları? >> "Full Buffering(Tam Tamponlama)" : Okuma ve yazma işlemleri sırasında ilk önce bahsi geçen tampon tamamiyle doldurulur. Daha sonra tek kalemde tampon boşaltılarak okuma ve yazma işlemi fiili olarak gerçekleştirilir. Tabiri caiz ise doldur, boşalt yöntemidir. Buradaki kritik nokta fiili olarak okuma/yazma yapılabilmesi için ilgili tamponun tamamiyle dolu olması gerekmekte, aksi halde iş bu fiiller gerçekleştirilmemektedir. Tampon burada tam kapasite ile kullanılmaktadır. "fflush" ve "fclose" fonksiyonları ilgili tamponu boşaltmaktadır. >> "Line Buffering(Satır Tamponlama)" : Okuma ve yazma işlemleri sırasında '\n' karakteri görülene kadar tamponlama yapılır. İş bu karakter görüldükten sonra tampon boşaltılmak suretiyle fiili olarak okuma/yazma işlemi gerçekleştirilir. Tampon boşaltılırken '\n' karakteri de boşaltılır. Bu tip tamponlama kullanmak için açık olan dosyamızın bir "text" dosyası olması gerekmektedir. "binary" modda açılan bir dosyayı bu tip tamponlama mekanizmasına çekebiliriz fakat saçma bir yaklaşım olacaktır. Yine unutmamalıyız ki '\n' görene kadarki karakterlerin adedi, tamponun büyüklüğünden fazla ise tampon dolacaktır. Bu nokta da yine tampon boşaltıldıktan sonra okuma/yazma işlemi yapılacaktır. "fflush" ve "fclose" fonksiyonları ilgili tamponu boşaltmaktadır. >> "No Buffering(Sıfır Tamponlama)" : Okuma ve yazma işlemleri sırasında tampon hiç kullanılmamaktadır. Direkt olarak okuma/yazma işlemi yapılmaktadır. Pekiyi akla şu iki soru gelmektedir; bir dosyanın varsayılan tamponlama modu nedir ve bir dosyanın tamponlama modu nasıl değiştirilir? >> C standartları bir dosyanın varsayılan tamponlama modu hakkında bir şey söylememiştir. Ancak şu üç dosya hakkında bir şeyler söylemiştir ki bu dosyalar "stdin", "stdout" ve "stderr" dosyalarıdır. Geriye kalan dosyaların varsayılan tamponlama mekanizması ilgili kütüphaneyi yazanların insifiyatına bırakılmıştır. Fakat mevcuttaki standart C kütüphaneleri genel olarak varsayılan durumda "Full Buffering(Tam Tamponlama)" modu esas almaktadır. "stdio.h" içerisinde tanımlanan "stdin", "stdout" ve "stderr" isimli dosyalar "FILE" yapı türünden birer adres belirtmektedirler. Tıpkı "fopen()" fonksiyonu ile elde ettiklerimiz gibi. Bu dosyaların "fd" değeleri de sırasıyla "0", "1" ve "2" numaralarıdır. Fakat bunlardan "1" ve "2" numaralı değerler aynı dosya nesnesini göstermektedir. İş bu "FILE" türünden nesneler, standart C fonksiyonlarında da kullanılabilirler. Örneğin, "fprintf(stdout, ...);". Dolayısıyla bu çağrı aslında "printf(...);" biçimindeki çağrıdan farksızdır. Bu üç "FILE" türden nesneyi("Dosya Bilgi Göstericileri) bizler kapatmamalıyız."stdin" ve "stdout" dosyalarının varsayılan tamponlama mekanizması için standartlar şöyle demiştir; Eğer interaktif olmayan bir aygıta yönlendirilmişlerse ki dosyalar interaktif aygıtlar değildir fakat terminal interaktiftir, hiç bir zaman satır tamponlamalı ya da sıfır tamponlamalı OLAMAZ. TAM TAMPONLAMALI OLMAK ZORUNDADIR. Ancak yönlendirilmemiş iseler HİÇ BİR ZAMAN TAM TAMPONLU OLAMAZLAR. Bu durumda ya satır tamponlu ya da sıfır tamponlu olabilirler. Klavye ve ekran, yani terminal, interaktif kabul edilirker, disk dosyaları interaktif kabul EDİLMEZLER. Aşağıda bu konuya ilişkin bir örnek verilmiştir: * Örnek 1, Yazının tamponda bekletilmesi: #include int main() { /* # OUTPUT # */ printf("Hello World"); /* * İşin başında "stdout" dosyası interaktif terminale yönlendirilmiş vaziyette. Dolayısıyla * satır tamponlama ya da sıfır tamponlama yapmakta. Sıfır tamponlama yapsaydı "Hello World" yazıdı direkt * ekrana çıkacaktı ve programın akışı sonsuz döngüye girecekti. Fakat görüyoruz ki satır tamponlama yapılmış. * Bu da demektir ki ya '\n' karakteri görülecek ya da "fflush()" ya da "fclose()" gibi fonk. çağrılacak ya da * programın akışı "return" deyimine gelmeli ki tampon boşaltılsın. Biz ise sonsuz döngü oluşturduğumuz için * yukarudaki şartlardan hiç biri sağlanmadığı için "Hello World" yazısı tamponda bekletilmektedir... * Microsoft'un C kütüphanesinde sıfır tamponlama yapmaktadır. Dolayısıyla o derleyicide derlediğimiz zaman * yazı direkt olarak ekrana basılacaktır. */ for(;;) ; return 0; } * Örnek 2, #include int main() { /* # OUTPUT # Hello World */ printf("Hello World"); // printf("Hello World\n"); // APPROACH - I : İmleç bir aşağı satıra geçecektir. // fflush(stdout); // APPROACH - II : İmleç bir aşağı satıra geçmeyecektir. /* * Yukarıdaki iki yöntemden birisini "glibc" kütüphanelerinde kullanmamız durumunda ilgili yazı * ekrana direkt olarak basılacaktır. */ for(;;) ; return 0; } * Örnek 3, #include int main() { /* # OUTPUT # Hello World */ /* * Fakat şöyle de bir şey vardır; "stdin" dosyasından okuma yapıldığında "stdout" dosyası * "flush" edilmektedir fakat bu durum C standartlarında bir zorunluluk değildir. */ printf("Hello World"); getchar(); for(;;) ; return 0; } * Örnek 4, İmleci bir aşağı satıra geçirmeden ilgili yazıya direkt ekrana bastırmanın diğer yolu: #include int main() { /* # OUTPUT # Hello World */ setbuf(stdout, NULL); printf("Hello World"); for(;;) ; return 0; } Diğer yandan "stderr" dosyasının varsayılan tamponlama mekanizması için standartlar şöyle demiştir; İster interaktif aygıta ister interaktif olmayan bir aygıt yönlendirilsin, HİÇ BİR ZAMAN TAM TAMPONLU OLAMAZ. Ancak satır ya da sıfır tamponlamalı olur. >> Dosyanın tamponlama modları iki fonksiyon ile değiştirilmektedir. Bu fonksiyonların isimleri sırasıyla "setbuf" ve "setvbuf" isimli fonksiyonlarıdır. Burada "setvbuf" fonksiyonu, işlevsellik açısından "setbuf" fonksiyonunu da kapsamaktadır. Fakat bu dosyaları kullanabilmek için, dosyayı açtıktan sonra hiç bir işlev yapmadan, direkt bu fonksiyonlara çağrı yapmalıyız. Aksi halde Tanımsız Davranışa yol açacağız. >>> "setbuf" fonksiyonu: Standart bir C fonksiyonudur ve parametrik yapısı aşağıdaki gibidir; #include void setbuf(FILE * stream, char * buf); Birinci parametresi bir "FILE" türünden adres, ikinci parametresi ise "buffer" olarak kullanılacak alanın başlangıç adresi. Böylelikle bizler kendi tamponumuzun kullanılmasını sağlayabiliriz. Bu fonksiyon tamponlama yöntemini değiştirmemektedir. Eğer ikinci parametreye NULL geçmeniz durumunda, "No Buffering(Sıfır Tamponlama)" moduna geçecektir. İkinci parametreye geçilen yeni tampon bölgesinin alanıda yine "BUFSIZ" büyüklüğünde olmalıdır. * Örnek 1, #include #include int main() { /* # q.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # A [A] [h] [m] [e] [t] [ ] [K] [a] [n] [d] [e] [m] [i] [r] [ ] [P] [e] [h] [l] [i] [v] [a] [n] [l] [i] [] [] [] [] [] [] [] */ FILE* f; if((f = fopen("q.txt", "r")) == NULL) exit_sys("fopen"); char buffer[BUFSIZ]; setbuf(f, buffer); int ch; ch = fgetc(f); putchar(ch); putchar('\n'); for(int i = 0; i < 32; ++i) printf("[%c]%c", buffer[i], i % 16 == 15 ? '\n' : ' '); putchar('\n'); fclose(f); return 0; } >>> "setvbuf" fonksiyonu: Standart bir C fonksiyonudur. Hem halihazırdaki tamponu değiştirmek hem tamponlama yöntemini hem de tamponun boyutunu değiştirmek için kullanılır. Parametrik yapısı aşağıdaki gibidir; #include int setvbuf(FILE * stream, char * buf, int type, size_t size); Birinci parametre "FILE" türünden adres, ikinci parametresi yeni tampon olarak kullanılacak alanın başlangıç adresi ki bu parametreye NULL geçilmesi durumunda tampon değiştirilmeyecektir, üçüncü parametre tamponlama modu ki şu sembolik sabitlerden birisi olmalıdır; -> _IOFBF : Full Buffering(Tam Tamponlama) -> _IOLBF : Line Buffering(Satır Tamponlama) -> _IONBF : No Buffering(Sıfır Tamponlama) Son parametre ise tamponun uzunluk bilgisidir. Programcı ikinci parametreye NULL adres geçip son parametre üzerinden halihazırdaki tamponun uzunluğunu da değiştirebilir. Eğer üçüncü parametreye "_IONBF" geçmemiz durumunda, ikinci ve son parametreler işlevsiz hale gelecektir. Fonksiyonumuz başarı durumunda "0" değerine, başarısızlık durumunda ise "non-zero" bir değere dönmektedir ve POSIX sistemlerinde "errno" değeri uygun biçimde değiştirilmektedir. Fakat halihazırdaki tamponlama modunu bize döndüren standart bir C fonksiyonu mevcut değildir. * Örnek 1, #include #include int main() { /* # q.txt # Ahmet Kandemir Pehlivanli */ /* # OUTPUT # A Ahmet Kandemir Pehlivanli�y< */ FILE* f; if((f = fopen("q.txt", "r")) == NULL) { fprintf(stderr, "cannot open file!...\n"); exit(EXIT_FAILURE); } /* * Yeni tampon bölgemizin çöp değerler ile hayata geldiğini * unutmayalım. */ char buffer[512]; if(setvbuf(f, buffer, _IOLBF, 512) != 0) { fprintf(stderr, "cannot set the buffer!...\n"); exit(EXIT_FAILURE); } int ch; ch = fgetc(f); putchar(ch); putchar('\n'); for(int i = 0; i < 32; ++i) putchar(buffer[i]); putchar('\n'); fclose(f); return 0; } Bu iki fonksiyona ek olarak "setbuffer" ve "setlinebuf" isimli iki fonksiyon daha vardır fakat bunlar ne C standartlarında ne de POSIX standartlarında yer alan fonksiyonlardır. Sadece "glibc" kütüphanesinde bulunurlar. Dolayısıyla bunların kullanılması pek tavsiye edilmezler. Öte yandan Windows sistemlerindeki C derleyicilerinde varsayılan, yani herhangi başka bir yere yönlendirilme yapılmamışsa, "stdout" sıfır tamponlamalıyken "stdin" satır tamponlamalıdır. Buna karşın UNIX/Linux sistemlerinde her iki dosya da satır tamponlamalıdır. Fakat unutmamalıyız ki "stdin", "stdout" ve "stderr" dosyaları ilgili derleyiciler tarafından kapatılacağı için otomatik olarak "flush" da edilmektedirler. Son olarak "stdin" ve "stdout" arasında bir senkronizasyon sağlanması açısından, "stdin" dosyasından okuma yapıldığında "stdout" dosyası da "flush" edilmektedir fakat bu durum C standartlarında garanti edilmemiştir ama çoğu C derleyicisi bu özelliktedir. * Örnek 1, #include int main() { /* * İlgili yazı Windows sistemlerinde ekrana basılacak fakat UNIX/Linux sistemlerinde basılmayacak */ printf("Hello World"); for(;;) ; return 0; } * Örnek 2, #include int main() { /* * İlgili yazı programın sonlanmasından doğan "flush" nedeniyle her zaman ekrana basılacaktır. */ printf("Hello World"); return 0; } * Örnek 3, #include int main() { /* * Fakat bizler programın sonlanmasını beklemeden, ilgili yazının sonuna '\n' karakteri koyarak, yazının * her iki sistemde de ekrana basılmasını garanti edebiliriz. Fakat bu durumda imleç bir alt satıra * geçecektir. */ printf("Hello World\n"); for(;;) ; return 0; } * Örnek 4, #include int main() { /* * Eğer bizler hem imleç bir alt satıra geçmesin hem de ilgili yazı ekrana her şekilde basılsın * istiyorsak, önümüzde iki seçenek vardır: * Ya "fflush(stdout)" çağrısı ile ilgili tamponu elle boşaltmak, * Ya "stdout" tamponunu işin başında sıfır tamponlamalı moda çekmek. */ setvbuf(stdout, NULL, _IONBF, 0); // APPROACH - II setbuf(stdout, NULL); // APPROACH - III printf("Hello World"); fflush(stdout); // APPROACH - I for(;;) ; return 0; } * Örnek 5, #include int main() { /* * "stdin" dosyasından okuma yapılmadan evvel "stdout" dosyası "flush" edilmektedir fakat * bu durum C standartlarında garanti altına alınmamıştır. Dolayısıyla ekrana ilgili * yazı her halükarda çıkacaktır eğer iş bu derleyici bu özelliği sunuyorsa. */ printf("Hello World"); getchar(); return 0; } UNIX/Linux sistemlerinde "stdin" dosyası, dosyaya yönlendirilmemiş ise satır tamponlamalı moddadır. Dolayısıyla bizler bir klavyeden bir karakter dahi okusak, '\n' karakteri görülene kadarki bilgiler tampona çekilecek ve bu tampondan bizlere bir karakter verilecektir. Tampon dolu olduğu müddetçe, her bir karakter okumak istediğimizde bu tampondan veri alacağız. Burada unutmamamız gereken nokta '\n' karakterinin kendisi de tampona çekilmesidir. Tabii "scanf", "getchar", "gets" gibi fonksiyonlar ortak tampondan çalışmaktadırlar. Yani bu fonksiyonlar da "stdin" dosyasından okuma yaparlar. * Örnek 1, #include #include #include void exit_sys(const char* msg); int main() { /* # INPUT # Ali */ /* # OUTPUT # [A] [l] [i] [ ] */ int ch; /* * Aşağıdaki "getchar()" çağrısında "Ali" yazısını girmemiz durumunda, * "Ali\n" komple "stdin" tamponuna çekilir ve bu tampondaki ilk * karakter olan 'A' karakteri geri döndürülür. */ ch = getchar(); printf("[%c]\n", ch); /* * "stdin" tamponu hala dolu olduğu için bizlere sıradaki karakter olan * 'l' karakteri tampondan döndürülür. */ ch = getchar(); printf("[%c]\n", ch); /* * "stdin" tamponu hala dolu olduğu için bizlere sıradaki karakter olan * 'i' karakteri tampondan döndürülür. */ ch = getchar(); printf("[%c]\n", ch); /* * "stdin" tamponu hala dolu olduğu için bizlere sıradaki karakter olan * '\n' karakteri tampondan döndürülür. */ ch = getchar(); printf("[%c]\n", ch); /* Bu noktada tampon boş olduğu için artık yeni bir satırlık bilgi isteyecektir. */ return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Pekiyi bizler ikinci "getchar()" çağrısında da klavyeden giriş almak istesek, nasıl bir yol izlemeliyiz? "fflush(stdin)" çağrısı geçersiz bir çağrıdır çünkü standartlara göre "read-only" modda açılmış bir dosya "flush" edilemezler. Fakat bazı derleyiciler bu özelliği desteklemektedir ama bundan kaçınmalıyız. Bu durumda geriye kalan şey tamponu elle boşaltmaktır. Çünkü "stdin" dosyasını sıfır tamponlu moda da ÇEKEMEYİZ. İlgili tamponu boşaltan özel bir fonksiyon da standartlarda mevcut olmadığından, bizler yeni bir fonksiyon yazmalıyız. Fakat tamponu boşaltırken "EOF" konumunu da kontrol etmemiz daha iyi olacaktır. * Örnek 1, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # Ali Kaan */ /* # OUTPUT # [A] [K] */ int ch; /* * Aşağıdaki "getchar()" çağrısında "Ali" yazısını girmemiz durumunda, * "Ali\n" komple "stdin" tamponuna çekilir ve bu tampondaki ilk * karakter olan 'A' karakteri geri döndürülür. */ ch = getchar(); printf("[%c]\n", ch); clear_stdin(); /* * "stdin" tamponu elle boşaltıldığı için artık klavyeden yeni bir giriş * isteyecektir. */ ch = getchar(); printf("[%c]\n", ch); return 0; } void clear_stdin(void) { int ch; /* * Aşağıda "EOF" durumunun neden sorgulandığı, ileriki derslerde * anlatılacaktır. */ while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } * Örnek 2, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # 120 */ /* # OUTPUT # [1] [20] [ ] */ int ch = 31; /* * Aşağıdaki "getchar()" çağrısında "120" sayısını girmemiz durumunda, * "120\n" biçiminde "stdin" tamponuna çekilir ve bu tampondaki ilk * karakter olan '1' karakteri geri döndürülür. */ ch = getchar(); printf("[%c]\n", ch); /* * Tamponda "20\n" karakterleri kaldı. Bu durumda "20" karakterleri * tampondan çekilecektir. Bu noktada artık tamponda '\n' karakteri * kaldı. */ scanf("%d", &ch); printf("[%d]\n", ch); /* * Bu noktada da tamponda son kalan '\n' karakteri çekildi ve tampon * tamamiyle boşaltıldı. */ ch = getchar(); printf("[%c]\n", ch); return 0; } void clear_stdin(void) { int ch; /* * Aşağıda "EOF" durumunun neden sorgulandığı, ileriki derslerde * anlatılacaktır. */ while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } "stdin" dosyası gerek "shell" üzerinden "<" sembolü gerek "dup" fonksiyonları kullanılarak bir dosyaya yönlendirildiği vakit, dosyanın sonuna geldiğinde "EOF" durumu ile karşılaşmaktadır. Fakat herhangi bir yönlendirme yapılmadığında da terminal aygıt sürücüsünden yönlendirilmiştir ki bu durumda da klavyeden girilenleri okuyacaktır. Fakat klavyede "EOF" durumu söz konusu DEĞİLDİR. İşte klavyeden giriş yaparken "EOF" etkisi oluşturmak için bir takım tuş kombinasyonları kullanmamız gerekiyor. Windows sistemlerinde "CTRL+Z", UNIX/Linux sistemlerinde "CTRL+D" tuş kombinasyonları bu etkiyi meydana getirmektedir. Bu tuş kombinasyonları girildiğinde açık olan dosyalar kapatılmaz sadece "EOF" etkisi oluşturulur. Klavyeden okuma yaparken, kullanıcının gerçektende EOF etkisi mi oluşturmak istediğini yoksa niyetinin sadece CTRL ve Z/D tuşlarına basmak olduğunu sorgulamamız gerekmektedir. Aksi halde kullanıcı bir işlem yapmak istersek yanlışlıkla "EOF" etkisi oluşturacaktır. Burada meydana getirilen "EOF" etkisi YALANCI BİR ETKİDİR. Unutmamalıyız ki normal bir dosyayı okurken "EOF" konumuna geldiğimiz zaman tekrar okuma yaparsak yine "EOF" konumunu okuyacağız fakat tuş kombinasyonlarında bu geçerli değildir. Çünkü yalanı bir "EOF" etkisidir. * Örnek 1, #include #include #include void clear_stdin(void); void exit_sys(const char* msg); int main() { /* # INPUT # ali CTRL + D */ /* # OUTPUT # Not EOF!!! >>> EOF <<< */ int ch; ch = getchar(); if(ch == EOF) printf(">>> EOF <<<\n"); else printf("Not EOF!!!\n"); return 0; } void clear_stdin(void) { int ch; while((ch = getchar()) != '\n' && ch != EOF) ; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } > Hatırlatıcı Notlar: >> "stdin" den okuma yapıldığında "stdout" tamponu boşaltılıyor fakat garanti altına alınmamıştır.