235 votes

C lire le fichier ligne par ligne

J'ai écrit cette fonction pour lire une ligne d'un fichier :

const char *readLine(FILE *file) {

    if (file == NULL) {
        printf("Error: file pointer is null.");
        exit(1);
    }

    int maximumLineLength = 128;
    char *lineBuffer = (char *)malloc(sizeof(char) * maximumLineLength);

    if (lineBuffer == NULL) {
        printf("Error allocating memory for line buffer.");
        exit(1);
    }

    char ch = getc(file);
    int count = 0;

    while ((ch != '\n') && (ch != EOF)) {
        if (count == maximumLineLength) {
            maximumLineLength += 128;
            lineBuffer = realloc(lineBuffer, maximumLineLength);
            if (lineBuffer == NULL) {
                printf("Error reallocating space for line buffer.");
                exit(1);
            }
        }
        lineBuffer[count] = ch;
        count++;

        ch = getc(file);
    }

    lineBuffer[count] = '\0';
    char line[count + 1];
    strncpy(line, lineBuffer, (count + 1));
    free(lineBuffer);
    const char *constLine = line;
    return constLine;
}

La fonction lit le fichier correctement, et en utilisant printf je vois que la chaîne constLine a été lue correctement aussi.

Cependant, si j'utilise la fonction comme ceci, par exemple :

while (!feof(myFile)) {
    const char *line = readLine(myFile);
    printf("%s\n", line);
}

printf sort du charabia. Pourquoi ?

1 votes

Utilisez fgets au lieu de fgetc . Vous lisez caractère par caractère et non ligne par ligne.

4 votes

Notez que getline() fait partie de POSIX 2008. Il peut y avoir des plates-formes de type POSIX sans lui, surtout si elles ne supportent pas le reste de POSIX 2008, mais dans le monde des systèmes POSIX, getline() est assez portable de nos jours.

357voto

mbaitoff Points 1505

Si votre tâche ne consiste pas à inventer la fonction de lecture ligne par ligne, mais simplement à lire le fichier ligne par ligne, vous pouvez utiliser un extrait de code typique impliquant la fonction getline() (voir la page du manuel aquí ):

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

int main(void)
{
    FILE * fp;
    char * line = NULL;
    size_t len = 0;
    ssize_t read;

    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(EXIT_FAILURE);

    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu:\n", read);
        printf("%s", line);
    }

    fclose(fp);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

20 votes

Plus précisément, cette getline est spécifique à GNU libc, c'est-à-dire à Linux. Cependant, si l'intention est d'avoir une fonction de lecture de ligne (par opposition à l'apprentissage du C), il existe plusieurs fonctions de lecture de ligne du domaine public disponibles sur le web.

14 votes

Pourquoi devrais-je faire ça ? Lisez le manuel, le tampon est réalloué à chaque appel, puis il doit être libéré à la fin.

36 votes

En if(line) Le contrôle est superflu. Appeler free(NULL) est essentiellement un no-op.

23voto

Gilles Points 37537

Dans votre readLine vous retournez un pointeur vers la fonction line (Strictement parlant, un pointeur vers son premier caractère, mais la différence n'est pas pertinente ici). Comme il s'agit d'une variable automatique (c'est-à-dire qu'elle se trouve "sur la pile"), la mémoire est récupérée au retour de la fonction. Vous voyez du charabia parce que printf a mis ses propres affaires sur la pile.

Vous devez renvoyer un tampon alloué dynamiquement à partir de la fonction. Vous en avez déjà un, c'est lineBuffer il ne vous reste plus qu'à le tronquer à la longueur souhaitée.

    lineBuffer[count] = '\0';
    realloc(lineBuffer, count + 1);
    return lineBuffer;
}

ADDED (réponse à la question de suivi dans le commentaire) : readLine renvoie un pointeur sur les caractères qui composent la ligne. Ce pointeur est ce dont vous avez besoin pour travailler avec le contenu de la ligne. C'est aussi ce que vous devez passer à free quand vous aurez fini d'utiliser la mémoire prise par ces personnages. Voici comment vous pourriez utiliser le readLine fonction :

char *line = readLine(file);
printf("LOG: read a line: %s\n", line);
if (strchr(line, 'a')) { puts("The line contains an a"); }
/* etc. */
free(line);
/* After this point, the memory allocated for the line has been reclaimed.
   You can't use the value of `line` again (though you can assign a new value
   to the `line` variable if you want). */

0 votes

@Iron : J'ai ajouté quelque chose à ma réponse, mais je ne suis pas sûr de votre difficulté donc il se peut que ce soit à côté de la plaque.

0 votes

@Iron : la réponse est que vous ne le libérez pas. Vous documentez (dans la documentation de l'API) le fait que le tampon retourné est malloqué et doit être libéré par l'appelant. Ainsi, les personnes qui utilisent votre fonction readLine écriront (avec un peu de chance !) un code similaire à l'extrait que Gilles a ajouté à sa réponse.

11voto

qrdl Points 17813

readLine() renvoie un pointeur vers une variable locale, ce qui provoque un comportement non défini.

Pour vous déplacer, vous pouvez :

  1. Créez une variable dans la fonction appelante et passez son adresse à la fonction readLine()
  2. Allouer de la mémoire pour line en utilisant malloc() - dans ce cas line seront persistants
  3. utiliser une variable globale, bien que ce soit généralement une mauvaise pratique

4voto

JeremyP Points 46808

Certaines choses ne vont pas avec l'exemple :

  • vous avez oublié d'ajouter \n à vos printfs. De même, les messages d'erreur doivent aller dans stderr, c'est-à-dire fprintf(stderr, ....

  • (ce n'est pas un gros problème mais) envisagez d'utiliser fgetc() plutôt que getc() . getc() est une macro, fgetc() est une fonction propre

  • getc() renvoie un int donc ch doit être déclaré comme un int . Ce point est important car la comparaison avec EOF seront traitées correctement. Certains jeux de caractères 8 bits utilisent 0xFF comme un caractère valide (ISO-LATIN-1 serait un exemple) et EOF qui est de -1, sera 0xFF si elle est affectée à un char .

  • Il y a un débordement de tampon potentiel à la ligne

    lineBuffer[count] = '\0';

    Si la ligne comporte exactement 128 caractères, count est de 128 au point qui est exécuté.

  • Comme d'autres l'ont souligné, line est un tableau déclaré localement. Vous ne pouvez pas retourner un pointeur vers celui-ci.

  • strncpy(count + 1) copiera au maximum count + 1 mais se terminera s'il atteint '\0' Parce que vous avez mis lineBuffer[count] a '\0' tu sais que ça n'arrivera jamais count + 1 . Cependant, si c'était le cas, cela ne mettrait pas un terme à l'affaire. '\0' sur, donc vous devez le faire. Vous voyez souvent quelque chose comme ce qui suit :

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • si vous malloc() une ligne à renvoyer (à la place de votre local char ), votre type de retour devrait être char* - laisser tomber le const .

1voto

user411313 Points 1920

Vous devez utiliser les fonctions ANSI pour lire une ligne, par exemple fgets. Après l'appel, vous avez besoin de free() dans le contexte de l'appel, par ex :

...
const char *entirecontent=readLine(myFile);
puts(entirecontent);
free(entirecontent);
...

const char *readLine(FILE *file)
{
  char *lineBuffer=calloc(1,1), line[128];

  if ( !file || !lineBuffer )
  {
    fprintf(stderr,"an ErrorNo 1: ...");
    exit(1);
  }

  for(; fgets(line,sizeof line,file) ; strcat(lineBuffer,line) )
  {
    if( strchr(line,'\n') ) *strchr(line,'\n')=0;
    lineBuffer=realloc(lineBuffer,strlen(lineBuffer)+strlen(line)+1);
    if( !lineBuffer )
    {
      fprintf(stderr,"an ErrorNo 2: ...");
      exit(2);
    }
  }
  return lineBuffer;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X