> Standart C kütüphanesindeki dosya fonksiyonları ile POSIX dosya fonksiyonları arasındaki ilişki: Standart C kütüphanelerindeki iş bu dosya fonksiyonları aslında birer sarma fonksiyonlarıdır. Yani UNIX/Linux dünyası için bu fonksiyonlar "open" POSIX fonksiyonunu çağırırken, Windows dünyası için Windows API fonksiyonlarını çağırmaktadır. C dilindeki "fopen" fonksiyonunun imzası aşağıdaki gibidir: #include FILE *fopen(const char *restrict pathname, const char *restrict mode); İş bu fonksiyonun geri dönüş değerinin türü "FILE" yapısının adresi. Peki bu "FILE" içerisinde neler var? Dosya betimleyicisi "fd" bilgisi olmak üzere, işe yarar diğer bilgiler bu yapı içerisinde tutulmaktadır. Standart C dosya fonksiyonlarının en belirgin bir özelliği "buffered" olmalarıdır. Yani kendi içlerinde "buffer" alan oluştururlar. Yani bilgiler geçici olarak bu "buffer" içerisinde saklanır. Fakat burada esasında olan bir "cache" sistemidir ama "buffer" terimi daha çok kullanılmaktadır. İş bu sebepten dolayıdır ki "FILE" yapısı içerisinde, bu "buffer" alanına ilişkin bilgiler de tutulmaktadır. Standart C dosya fonksiyonları "buffered" oldukları için sistem fonksiyonları daha az çağrılmaktadır. Dolayısıyla daha hızlıdırlar. Aşağıda buna bir örnek verilmiştir: * Örnek 1, /* Version - I */ #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # 10 20 30 30 20 10 */ int fd; if( (fd = open("q.txt", O_RDONLY)) == -1) exit_sys("open"); char ch; ssize_t result; while( (result = read(fd, &ch, 1)) > 0 ) putchar(ch); if( result == -1 ) exit_sys("read"); close(fd); putchar('\n'); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } /* Version - II */ #include #include #include #include void exit_sys(const char* msg); int main() { /* # OUTPUT # 10 20 30 30 20 10 */ int fd; if( (fd = open("q.txt", O_RDONLY)) == -1) exit_sys("open"); char ch; ssize_t result; char buffer[512]; while( (result = read(fd, buffer, 512)) > 0 ) for(int i = 0; i < result; ++i) putchar(buffer[i]); if( result == -1 ) exit_sys("read"); close(fd); putchar('\n'); return 0; } void exit_sys(const char* msg) { perror(msg); exit(EXIT_FAILURE); } Burada iki numaralı versiyon daha hızlı çalışacaktır çünkü sistem fonksiyonu daha az çağrılacağı için prosesin "mode" durumu daha az bir şekilde "kernel mode" a çekilecektir. Standart C dosya fonksiyonları da bu şekilde "buffer" kullanmaktadır. Örneğin, "fgetc()" fonksiyonu ile bir bayt bile okumak istesek arka planda ilgili "buffer" kadarlık alan okunur fakat bize sadece bir baytlık kısmı döndürür. Aynı fonksiyonu tekrar çağırmamız durumunda yeniden okuma yapmaz, "buffer" içerisinden bir sonraki baytlık alanı döndürür. Bu işlem ilgili "buffer" tamamiyle okunana kadar devam eder. Bu aşamada tekrardan "fgetc" fonksiyonunu çağırmamız durumunda, yeniden bir "buffer" lık kadar okuma yapılır. Bu "buffer" alanının büyüklüğü "stdio.h" başlık dosyası içerisinde tanımlanan "BUFSIZ" sembolik sabiti kadardır. Standart C dosya fonksiyonlarındaki iş bu "buffer" sadece okumanın yapılacağı bir "buffer" değil hem okumanın hem de yazmanın yapılabileceği bir "buffer" alanıdır. Dolayısıyla yazma işleminden evvel önce bu "buffer" alanına yazılmaktadır. Bu noktada da "write" sistem fonksiyonu devreye girmektedir. Bir "buffer" alanının "write" fonksiyonu ile diske aktarılması işlemine de "flush" işlemi denmektedir. Eğer bizler "fflush" isimli standart C fonksiyonunu çağırırsak, "buffer" içerisindeki bilgiler diske yazılacaktır. Aksi halde "buffer" alanının dolması beklenecektir. "fgetc" fonksiyonunun karşılığı da bu durumda "fputc" fonksiyonudur. Son olarak "fclose" fonksiyonu çağrıldığında ilgili "buffer" alanından diske yazım yapılmaktadır. Yine unutmamalıyız ki "fflush" fonksiyonunu çağırabilmek için ilgili dosyanın "w" ya da "r+" modda açılması gerekmektedir. Anımsanacağı üzere C dilindeki dosya açmakta kullanılan standart fonksiyonlar bize "FILE" türünden bir yapının adresini döndürmektedir. İş bu "FILE" yapısına aslında "stream" denilmekte olup, kurs boyunca bizler "Dosya Bilgi Göstericisi" ismi ile anacağız. Genel olarak "stream" denildiği zaman, özellikle haberleşme dünyasında, istenildiği kadar "bayt" ın okunabildiği sistemler akla gelmektedir. Tipik bir "FILE" yapısı aşağıdaki elemanlara sahiptir: (https://elixir.bootlin.com/uclibc-ng/latest/source/libc/sysdeps/linux/common/bits/uClibc_stdio.h#L212) struct __STDIO_FILE_STRUCT { unsigned short __modeflags; #ifdef __UCLIBC_HAS_WCHAR__ unsigned char __ungot_width[2]; /* 0: current (building) char; 1: scanf */ /* Move the following futher down to avoid problems with getc/putc * macros breaking shared apps when wchar config support is changed. */ /* wchar_t ungot[2]; */ #else /* __UCLIBC_HAS_WCHAR__ */ unsigned char __ungot[2]; #endif /* __UCLIBC_HAS_WCHAR__ */ int __filedes; #ifdef __STDIO_BUFFERS unsigned char *__bufstart; /* pointer to buffer */ unsigned char *__bufend; /* pointer to 1 past end of buffer */ unsigned char *__bufpos; unsigned char *__bufread; /* pointer to 1 past last buffered read char */ #ifdef __STDIO_GETC_MACRO unsigned char *__bufgetc_u; /* 1 past last readable by getc_unlocked */ #endif /* __STDIO_GETC_MACRO */ #ifdef __STDIO_PUTC_MACRO unsigned char *__bufputc_u; /* 1 past last writeable by putc_unlocked */ #endif /* __STDIO_PUTC_MACRO */ #endif /* __STDIO_BUFFERS */ #ifdef __STDIO_HAS_OPENLIST struct __STDIO_FILE_STRUCT *__nextopen; #endif #ifdef __UCLIBC_HAS_WCHAR__ wchar_t __ungot[2]; #endif #ifdef __STDIO_MBSTATE __mbstate_t __state; #endif #ifdef __UCLIBC_HAS_XLOCALE__ void *__unused; /* Placeholder for codeset binding. */ #endif #ifdef __UCLIBC_HAS_THREADS__ int __user_locking; __UCLIBC_IO_MUTEX(__lock); #endif /* Everything after this is unimplemented... and may be trashed. */ #if __STDIO_BUILTIN_BUF_SIZE > 0 unsigned char __builtinbuf[__STDIO_BUILTIN_BUF_SIZE]; #endif /* __STDIO_BUILTIN_BUF_SIZE > 0 */ }; Fakat unutmamalıyız ki "FILE" yapısının içerisindeki bu elemanlar, C standartlarında açıklanmış değil. Çünkü bu, implementasyonu yapan kişilere bırakılmış. İş bu elemanları kısaca özetlemek gerekirse; -> İşletim sistemi düzeyinde okuma/yazma işlemleri için gereken dosya betimleyicisi("fd") -> Sistem fonksiyonlarının çağırımını azaltmak için kullanılan tamponun başlangıç adresini tutan bir gösterici -> İlgili tamponda en son kalınan noktayı gösteren bir gösterici -> Tamponun uzunluğunu tutan bir eleman ya da tamponun bittiği yeri gösteren bir gösterici -> ... Pekiyi "FILE" yapısının yer tahsilatı nasıl yapılmaktadır? Bu konuda bir kaç yaklaşım söz konusudur. Bunlardan ilki iş bu yapı nesnesini dinamik ömürlü bir şekilde hayata getirmek ki ilgili alanın geri verilmesi "fclose()" fonksiyon çağrısı sırasnıda yapılacaktır. İkinci ise "FILE" türden elemanları olan statik bir diziden bizlere eleman vermesi biçimindedir. Üçüncü yöntem ise bu yöntemleri karma bir şekilde kullanılmasıdır. > Hatırlatıcı Notlar: >> "musl libc", "diet libc", "Standart C Library by P.J. Plauger" : Çeşitli C kütüphanelerini, özellikle "stdio", incelemek için.