2010-08-01 9 views
4

J'essaie de lire tout le contenu d'un fichier texte. Voici le code que j'ai écrit.Lire tout le contenu d'un fichier texte - C

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

#define PAGE_SIZE 1024 

static char *readcontent(const char *filename) 
{ 
    char *fcontent = NULL, c; 
    int index = 0, pagenum = 1; 
    FILE *fp; 
    fp = fopen(filename, "r"); 

    if(fp) { 
     while((c = getc(fp)) != EOF) { 
      if(!fcontent || index == PAGE_SIZE) { 
       fcontent = (char*) realloc(fcontent, PAGE_SIZE * pagenum + 1); 
       ++pagenum; 
      } 
      fcontent[index++] = c; 
     } 
     fcontent[index] = '\0'; 
     fclose(fp); 
    } 
    return fcontent; 
} 

static void freecontent(char *content) 
{ 
    if(content) { 
     free(content); 
     content = NULL; 
    } 
} 

C'est l'utilisation

int main(int argc, char **argv) 
{ 
    char *content; 
    content = readcontent("filename.txt"); 
    printf("File content : %s\n", content); 
    fflush(stdout); 
    freecontent(content); 
    return 0; 
} 

Depuis que je suis nouveau à C, je me demande si ce code semble parfait? Voyez-vous des problèmes/améliorations?

Compilateur utilisé: GCC. Mais ce code devrait être multi-plateforme.

Toute aide serait appréciée.

Modifier

Voici le code mis à jour avec fread et ftell. Je me demande quelle sera la complexité relative de cette fonction?

+2

Je pense à peine que l'utilisation de ce nom de fichier vous donnera beaucoup de félicitations –

+1

ahh .. Désolé pour cela. Je testais et j'ai oublié de l'enlever. Extremement Désolé. –

+0

Je pense qu'en général, vous devriez essayer de travailler en morceaux fixes; Dans ce cas, vous lirez dans PAGE_SIZE octets à la fois (ou moins si c'est le dernier morceau) et en imprimant chaque morceau comme vous les lisez. – wj32

Répondre

7

Vous devriez essayer regarder dans les fonctions fsize (A propos fsize, voir ci-dessous la mise à jour) et fread. Cela pourrait être une amélioration énorme de la performance. Pour obtenir la taille du fichier que vous lisez, utilisez fsize Utilisez cette taille pour effectuer une allocation de mémoire uniquement. (A propos de fsize, voir mise à jour ci-dessous L'idée d'obtenir la taille du fichier et de faire un alloc est toujours la même).

Utilisez fread pour effectuer une lecture de bloc du fichier. Ceci est beaucoup plus rapide que la lecture unique du fichier.

Quelque chose comme ceci:

long size = fsize(fp); 
fcontent = malloc(size); 
fread(fcontent, 1, size, fp); 

Mise à jour

Je ne sais pas que fsize est multi-plateforme, mais vous pouvez utiliser cette méthode pour obtenir la taille du fichier:

fseek(fp, 0, SEEK_END); 
size = ftell(fp); 
fseek(fp, 0, SEEK_SET); 
+0

Merci. J'ai cherché la documentation de 'fsize', mais je n'en ai pas trouvé. Est-ce une fonction indépendante de la plateforme? Comment 'fsize' peut-il indiquer la taille du fichier sans lire le fichier entier? –

+0

Juste mis à jour ma réponse avec un remplacement de fsize :) –

+0

'fsize' semble être spécifique à Windows. 'stat (2)' est l'équivalent UNIX. – Wang

2

Personnes souvent realloc à deux fois la taille existante pour obtenir le temps constant amorti au lieu de linéaire. Cela rend le tampon pas plus de deux fois plus grand, ce qui est généralement correct, et vous avez la possibilité de réattribuer à la bonne taille une fois que vous avez terminé.

Mais encore mieux vaut stat(2) pour la taille du fichier et d'allouer une fois (avec un peu d'espace si la taille du fichier est volatile).

Aussi, pourquoi ne pas fgets(3) au lieu de lire caractère par caractère, ou, mieux encore, mmap(2) le tout (ou le morceau pertinent si c'est trop grand pour la mémoire).

2

Il est probablement plus lent et certainement plus complexe que:

while((c = getc(fp)) != EOF) { 
    putchar(c); 
} 

qui fait la même chose que votre code.

0

Sur les systèmes POSIX (par exemple Linux), vous pouvez obtenir le même effet avec l'appel système mmap qui mappe tous vos fichiers en mémoire. Il a une option pour mapper ce fichier copier sur écrire, de sorte que vous écraser votre fichier si vous modifiez le tampon.

Cela serait généralement beaucoup plus efficace, puisque vous laissez autant que vous le pouvez au système. Pas besoin de faire realloc ou similaire. En particulier, si vous lisez seulement et que plusieurs processus le font en même temps, il n'y aurait qu'une seule copie en mémoire pour l'ensemble du système.

+0

Je pense que vous êtes confus au sujet de ce que signifie copier-sur-écriture. Si le fichier est mappé copy-on-write (private), la map est à l'origine une simple référence au fichier sur disque, mais toute modification apportée à celle-ci entraînera une copie des données locales à votre processus. Si le mappage est partagé, vos modifications seront écrites dans le fichier et visibles par d'autres processus. –

+0

@R. une référence au fichier sur disque? sûr que tout 'mmap' fait que c'est l'idée. Ce que je voulais dire, c'est que le système peut contenir toutes les pages que vous ne modifiez pas dans son cache de pages et partager ce cache entre les processus. Cela est vrai pour deux situations: (1) tant que vous mappez les choses en lecture seule ou (2) si vous utilisez la copie sur écriture et que vous ne modifiez pas le contenu. Donc, en général, si vous pensez que vous avez besoin d'un accès aléatoire à l'ensemble du contenu d'un fichier, 'mmap' est presque toujours la meilleure stratégie. 'fread' et les variantes doivent être limités aux cas où vous n'avez besoin que d'un accès partiel au fichier à un moment donné. –

1

D'après une lecture rapide, j'ai peut-être manqué quelques problèmes. En premier lieu, a = realloc(a, ...); est erroné. Si realloc() échoue, il renvoie NULL, mais ne libère pas la mémoire d'origine. Puisque vous réaffectez à a, la mémoire d'origine est perdue (c'est-à-dire, il s'agit d'une fuite de mémoire). La bonne façon de le faire est de faire: tmp = realloc(a, ...); if (tmp) a = tmp;

Deuxièmement, à propos de la détermination de la taille du fichier en utilisant fseek(fp, 0, SEEK_END);, notez que cela peut ou ne peut pas fonctionner. Si le fichier n'est pas un accès aléatoire (tel que stdin), vous ne pourrez pas revenir au début pour le lire. En outre, fseek() suivi de ftell() peut ne pas donner un résultat significatif pour les fichiers binaires. Et pour les fichiers texte, il peut ne pas vous donner le bon nombre de caractères qui peuvent être lus. Il y a quelques informations utiles sur ce sujet sur comp.lang.c FAQ question 19.2.

Aussi, dans votre code d'origine, vous ne définissez pas index-0 quand il est égal à PAGESIZE, donc si la longueur de votre fichier est supérieure à 2*PAGESIZE, vous écraserez la mémoire tampon.

Votre fonction freecontent():

static void freecontent(char *content) 
{ 
    if(content) { 
     free(content); 
     content = NULL; 
    } 
} 

est inutile. Il définit uniquement une copie de content à NULL. Il est comme si vous avez écrit une fonction setzero comme ceci:

void setzero(int i) { i = 0; } 

Une meilleure idée est de garder une trace de la mémoire vous et rien libre plus ou moins nécessaire.

Vous ne devez pas jeter la valeur de retour de malloc() ou realloc() en C, étant donné qu'un void * est converti implicitement à tout autre type de pointeur d'objet dans C.

espoir qui aide.

+0

'stdin' est recherché s'il fait référence à un fichier pouvant être recherché. Ce n'est pas possible si c'est un périphérique interactif, pipe, etc. 'fseek' /' ftell' ** est ** fiable sur les fichiers binaires sur tout système raisonnable. Oui, le grand-père standard C-dans les implémentations héritées où les fichiers binaires peuvent avoir des octets aléatoires à zéro, mais nous sommes en 2010 et tous les systèmes actuels ont de vrais fichiers binaires. Le mode texte ne devrait tout simplement pas être utilisé en raison d'un comportement imprévisible et bogué. Juste dépouiller le '\ r' vous-même. –

+0

@R ..: Sur mon Mac, 'fseek (stdin, 0, SEEK_END)' réussit, 'ftell()' renvoie 0, et je suis capable de lire autant de caractères de 'stdin' que je veux. Sur linux, 'fseek (stdin, 0, SEEK_END);' aboutit à 'Illegal seek' (le même programme). Je préférerais une approche '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '. –

+0

Sauf s'il y a une raison pour laquelle vous avez besoin de tout le fichier en mémoire, vous devriez probablement suivre la réponse de msw, qui n'a pas de cas d'échec et d'exactitude correcte. BTW si vous voulez enlever '\ r' (par exemple à partir de fichiers texte Windows), vous devrez le faire vous-même de toute façon. Seul Windows et le Mac hérité (pré-OSX) ont des opérations de "mode texte" qui altèrent les données. POSIX exige que le mode texte se comporte de manière identique au mode binaire, et ce sur OSX, Linux, etc. –

1

Un problème que je peux voir ici est la variable index qui est non décroissante. La condition if(!fcontent || index == PAGE_SIZE) ne sera donc vraie qu'une seule fois. Je pense donc vérifier devrait être comme index%PAGE_SIZE == 0 au lieu de index == PAGE_SIZE.

Questions connexes