15 Jun 2009

Odczytywanie plików w języku C

Notatka

Artykuł ten pochodzi ze starej wersji tego bloga (rok 2006) i ma na celu pokazanie, jak poprawnie odczytywać pliki dowolnego typu w języku C/C++. Co ciekawe testowałem jego działanie całkiem niedawno i w obu przypadkach działa to poprawnie - jednakże pamiętam, że miałem pewien problem przy czytaniu danych - pewnie były to dane binarne.
Różnice w działaniu są widoczne przy odczytywaniu pliku który ma wielkość 0 bajtów.

Wstęp

Gdy przychodzi do odczytywania plików programista myśli najczęściej coś w stylu "dopóki nie koniec pliku, czytaj i przetwarzaj dane", co kończy się w taki sposób.

Przykład niepoprawny!!!

#include <stdio.h>
#include <stdlib.h>

#define MYFILE "test.txt"

int main(int argc, char **argv) {
  FILE *fp;
  char buf[BUFSIZ];
  int i;
  
  if ((fp = fopen(MYFILE, "r")) == NULL) {
    perror (MYFILE);
    return (EXIT_FAILURE);
  }
  
  i = 0;
  while (!feof(fp)){
    fgets(buf, BUFSIZ, fp);
    printf ("Line %4d: %s", i, buf);
    i++;
  }
  printf("\n");
  fclose(fp);
  return(EXIT_SUCCESS);
}
Z tego co się orientuje taka konstrukcja jest zupełnie poprawna w językach takich jak
  • Pascal
  • PHP
Jednakże w języku C ten kawałek kodu zawiera poważny błąd, ponieważ funkcja feof() służy do sprawdzenia czy koniec koniec pliku został już osiągnięty, powoduje to "przeczytanie" podwójnie ostatniej linii z pliku wejściowego. Jak można przeczytać w manualu:
The feof function

Synopsis

1 #include <stdio.h>
int feof(FILE *stream);

Description
2 The feof function tests the end-of-file indicator for the stream pointed to by stream.

Returns
3 The feof function returns nonzero if and only if the end-of-file indicator is set for stream.
Funkcja feof() testuje strumień ma ustawiony znacznik oznaczający koniec pliku, a nie czy nastąpił sam koniec pliku. Oznacza to że taki identyfikator jest ustawiany przez inną funkcję - funkcję która odczytuje dane. Można przyjąć, że tra funkcja czyta wszystkie dane, ale w momencie napotkania końca pliku ustawia znacznik EOF na strumieniu.

Wersja poprawna

#include <stdio.h>
#include <stdlib.h>

#define MYFILE "test.txt"

int main(int argc, char **argv) {
  FILE *fp;
  char buf[BUFSIZ];
  int i;
  
  if ((fp = fopen(MYFILE, "r")) == NULL) {
    perror (MYFILE);
    return (EXIT_FAILURE);
  }
  
  i = 0;

  while (fgets(buf, BUFSIZ, fp) != NULL) {
    printf ("Line %4d: %s", i, buf);
    i++;
  }
  printf("\n");
  if (feof(fp)) {
    printf("EOF Reached\n");
  }
  
  fclose(fp);
  return(EXIT_SUCCESS);
}
By uniknąć tej przykrej przypadłości należy czytać pliki tak jak wyżej. Dzięki temu, zawsze jest sprawdzany wynik działania funkcji read - pozwala to ustawić znaczink EOF na strumieniu.

Inne przykłady poprawego czytania wejścia

int total = 0;
while (fscanf(fp, "%d", &num) == 1) {
  total += num;
}
printf ("Total is %d\n", total);
int c; 
while ((c = fgetc(fp)) != EOF) {
  putchar (c);
}
To na tyle ;).

1 comment:

  1. To nie jest trudne, ale czasami pisząc program trzeba go debugować polecam ten krótki tutorial GDB Tutrial

    ReplyDelete