Odczytywanie plików w języku C

Informacja

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.

Podpowiedź

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 ;).

Comments

comments powered by Disqus