16 Ocak 2016 Cumartesi

feof - End Of File

Giriş
Dosyanın sonuna gelindiği kontrolü (EOF) hata yapmaya açık bir konu. İnternette "EOF kontrolü yapan kodum neden çalışmıyor" şeklinde bir çok soru görülebilir.

EOF Karakteri Nedir
EOF karakteri diye bir karakter aslında yok. İşletim sistemi bir şekilde dosyanın boyunu biliyor ve sonuna geldiğimizde bize EOF - genelde -1 olarak tanımlıdır - dönüyor. Burada küçük bir açıklama yapmak lazım. Windows C Runtime ASCII 26 karakterini EOF olarak yorumluyor. Bu sadece Windows'a mahsus. Linux'ta böyle bir kural yok. Dolayısıyla Windows'ta binary bir dosyayı text olarak açarsak ve içinde tesadüfen ASCII 26 karakteri varsa henüz dosyanın sonuna gelmeden EOF alabiliriz. Aşağıdaki örnekte binary tar.gz dosyasını text modunda açınca bu durum görülebilir.
int oneChar;
iFile = fopen("myFile.tar.gz", "r");
while ((oneChar = fgetc(iFile)) != EOF) {
        printf("%d ", oneChar);
}
Aslında dosya şöyle açılmalıydı.
fopen("myFile.tar.gz", "rb");

feof nasıl çalışır?
Benim için akımların (stream) nasıl bu kontrolü yaptığını anlatan bu soruyu görmek ufuk açıcı bir deneyim oldu.

C dilindeki bu metod sadece ve sadece bir akımın eof bayrağının atanıp atanmadığını kontrol eder. Yani bu metod okuma işlemi yapmaz!
Bu bayrağın kaldırılması için, akımın en son karakterini de okuduktan sonra, bir tane daha okuma işlemi yapmamız gerekir! Aynı kural C++ için de geçerlidir.
Konuyu açıklayan küçük bir örnek
std::istringstream stream("a");
char ch;
if (stream >> ch) {
   std::cout << "At eof? " << std::boolalpha << stream.eof() << "\n";
   std::cout << "good? " << std::boolalpha << stream.good() << "\n";
}
Bu kod şunu döner
false //Aslında dosya sonuna geldik ama henüz geçmedik. EOF false
true //Aslında dosya sonuna geldik ama henüz geçmedik. Good true

Döngü Koşulu Olarak feof Kullanmak
Yukarıdaki açıklamayı okuduktan sonra döngü koşulu olarak feof kullanmak istiyorsa, bir tane read işleminin hata döndüreceğini kabul etmesi gerektiğini kabul ederek işe başlamak gerekir. Dolayısıyla her read işleminin sonucunu kontrol etmemiz lazım gelir. Yani mantra şu
Don't use good() or eof() to detect eof before you try to read any further
Peki bu zahmete neden gireriz? Aklıma gelen tek örnek dosyada istenilen formata uymayan geçersiz bir satır varsa, geçersiz satır ile EOF arasındaki farkı ayırt etmek için kullanımı.

Yanlış Kullanım Örneği - Yapmayın!
Örnek'te dosyanın sonuna gelinceye kadar sayılar okunuyor. Son sayılar da okunduktan sonra feof bir kere daha çağırılıyor. Ancak henüz akımın sonunu geçmedik. Yani Bir read işlemi akımın eof bayrağını atamadı. Dolayısıyla feof true döndürür ve fscanf bir kere daha çalışır. Bu sefer fscanf akımın eof bayrağını atar. Ancak fscanf için döndürdüğü sonuç kontrolü yapılmadığı için printf çalıştırılır. Ve son satır iki kere gösterilir!
#include<stdio.h>

int main()
{

  int i;
  FILE * f;
  f=fopen("inputA.txt","r");

  while (!feof(f)){
    fscanf(f, "%i", &i);
    printf("%i\t", i);
  }
  printf("\n");

}
C++ için Yanlış Kullanım Örneği - Yapmayın!
Bu örnek mantrayı ihlal eder.
ifstream foo("foo.txt");

while (foo.good()){
    string bar;
    getline(foo, bar);
    cout << bar << endl;
}
Bu örnek te mantrayı ihlal eder.
while(!inStream.eof()){
  int data;
   inStream >> data;
}

Doğru Kullanım Örneği
İllaki feof ile çalışmak istiyorsak read işleminden sonra okumanın başarılı olduğunu kontrol etmek gerekir.
while (!feof(fp)) {
  ++lineno;
  while (nfields == (nret = fscanf(fp, "%f,", &vec[0]))) {
    // do something with the vector read
    ++lineno;
  }

  if (ferror(fp)) {
    // handle the error, usually exit/return
  } else if (nret != EOF) {
    fprintf(stderr, "warning: ignoring malformed line %zu\n", lineno);
    fscanf(fp, "%*[^\n]");
  }
}
C++'ta şöyle yapılır
while( !in.eof() ) 
{  
   int data;
   in >> data;
   if ( in.fail() ) /* handle with break or throw */; 
   // now use data
}

Döngü Koşulu Olarak Read İşlemi Kullanmak
Bu örneklerde dosyada hatalı satır olması ile ilgilenmeyiz. Sadece tüm satırları okumaya çalışırız. Aşağıdaki örneklerin hepsi doğru çalışır.

fgetc kullanımı
while döngüsünden çıktından sonra ferror kontrolü bence isteğe bağlı. EOF'tan dolayı mı yoksa okuma hatasından dolayı mı çıktığımızı anlamaya yarar.
/*
 * ch MUST be an int. Otherwise you can't distinguish between
 * (unsigned char)255 and EOF, and this won't work.
 */
int ch;

while ((ch = fgetc(fp)) != EOF) {
    // do something with ch
}
if (ferror(fp)) {
    // handle the error, usually exit or return
} else {
    // continue execution
}
fgets kullanımı
while döngüsünden çıktından sonra ferror kontrolü bence isteğe bağlı. EOF'tan dolayı mı yoksa okuma hatasından dolayı mı çıktığımızı anlamaya yarar.
while (fgets(buffer, buffer_size, fp)) {
    // do something with buffer
}
if (ferror(fp)) {
    // handle the error, usually exit or return
} else {
    // continue execution
}
C++ ile getline
C++ ile getline kullanarak bir satırı okumak mümkün. Döngüden EOF yüzünden mi yoksa satır sonu geldiği için mi çıkıldığını anlamak kolay.
while(getline(file,line) && !file.fail()) {}
C++ ile stream
Örnek
int data;
while(inStream >> data){
 
}
fread kullanımı
fread yazısına taşıdım.


Hiç yorum yok:

Yorum Gönder