C читать файл построчно


184

Я написал эту функцию для чтения строки из файла:

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;
}

Функция правильно читает файл, и с помощью printf я вижу, что строка constLine также правильно прочитана.

Однако, если я использую функцию, например, так:

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

printf выводит бред. Зачем?


Используйте fgetsвместо fgetc. Вы читаете символ за символом вместо строки за строкой.
Шив

3
Обратите внимание, что getline()это часть POSIX 2008. Могут существовать POSIX-подобные платформы без него, особенно если они не поддерживают остальную часть POSIX 2008, но в мире систем POSIX в getline()наши дни довольно переносимы.
Джонатан Леффлер

Ответы:


305

Если ваша задача состоит не в том, чтобы изобрести функцию построчного чтения, а просто в построчном чтении файла, вы можете использовать типичный фрагмент кода, включающий эту getline()функцию (см. Страницу руководства здесь ):

#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);
}

83
Это не портативно.
JeremyP

16
Точнее, это getlineотносится к GNU libc, то есть к Linux. Однако, если цель состоит в том, чтобы иметь функцию чтения строк (в отличие от изучения C), в Интернете доступно несколько функций чтения строк из общественного достояния.
Жиль "ТАК - перестань быть злым"

11
Почему я должен делать это? Прочтите руководство, буфер перераспределяется при каждом вызове, затем его следует освободить в конце.
mbaitoff

29
if(line)Проверка является излишней. Вызов free(NULL)по сути не является опцией.
Аромат

50
Для тех, кто сказал, что этот getline специфичен для GNU libc, «оба getline () и getdelim () изначально были расширениями GNU. Они были стандартизированы в POSIX.1-2008».
willkill07

37
FILE* filePointer;
int bufferLength = 255;
char buffer[bufferLength];

filePointer = fopen("file.txt", "r");

while(fgets(buffer, bufferLength, filePointer)) {
    printf("%s\n", buffer);
}

fclose(filePointer);

Для меня это приводит к перезаписи каждой строки следующей. Посмотрите этот вопрос на основе приведенного выше ответа.
Цезарь Кобуз

5
Почему актерский состав (FILE*) fp? Разве это не fpуже, FILE *а также fopen()возвращает FILE *?
Бухгалтер م

1
Если вы согласны с ограничением строк определенной длиной, это лучший ответ. В противном случае использование getlineявляется хорошей альтернативой. Я согласен, что FILE *актеры не нужны.
Theicfire

Я удалил ненужный бросок, добавил переменную для длины буфера и изменен , fpчтобы filePointerдля большей ясности.
Роб

21

В вашей readLineфункции вы возвращаете указатель на lineмассив (строго говоря, указатель на его первый символ, но здесь разница не имеет значения). Поскольку это автоматическая переменная (т. Е. Она «в стеке»), память возвращается после возврата функции. Вы видите бред, потому printfчто положил свои вещи в стек.

Вам необходимо вернуть динамически выделенный буфер из функции. У вас уже есть один, это lineBuffer; все, что вам нужно сделать, это обрезать его до желаемой длины.

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

ДОБАВЛЕНО (ответ на дополнительный вопрос в комментарии): readLineвозвращает указатель на символы, составляющие строку. Этот указатель - то, что вам нужно для работы с содержимым строки. Это также то, что вы должны передать, freeкогда закончили использовать память, занятую этими персонажами. Вот как вы можете использовать эту readLineфункцию:

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). */

@Iron: я добавил кое-что к своему ответу, но я не уверен, в чем твоя сложность, так что она может быть не на должном уровне.
Жиль "ТАК - перестань быть злым"

@Iron: ответ в том, что вы не освобождаете это. Вы документируете (в документации по API) тот факт, что возвращаемый буфер является malloc'd и нуждается в освобождении вызывающей стороной. Тогда люди, которые используют вашу функцию readLine, (будем надеяться!) Напишут код, похожий на фрагмент, добавленный Жилем к его ответу.
JeremyP

15
//open and get the file handle
FILE* fh;
fopen_s(&fh, filename, "r");

//check if file exists
if (fh == NULL){
    printf("file does not exists %s", filename);
    return 0;
}


//read line by line
const size_t line_size = 300;
char* line = malloc(line_size);
while (fgets(line, line_size, fh) != NULL)  {
    printf(line);
}
free(line);    // dont forget to free heap memory

1
Есть некоторые проблемы с этим кодом: fopen_sделает код непереносимым. printfбудет искать спецификаторы формата, а не печатать знаки процента и следующие символы, как они есть . Нулевые байты заставят все символы в остальной части строки исчезнуть. (Не говорите мне, что нулевые байты не могут произойти!)
hagello

И, кстати, вы не решаете проблему. ОП описывает, что возвращаемое значение его функции исчезает. Я не вижу, чтобы вы решали эту проблему.
Гагелло

@ Хартли Я знаю, что это старый комментарий, но я добавляю его, чтобы кто-то не прочитал его комментарий и не попытался освободить (строку) в цикле. Память для строки выделяется только один раз до начала цикла, поэтому она должна быть освобождена только один раз после окончания цикла. Если вы попытаетесь освободить строку внутри цикла, вы получите неожиданные результаты. В зависимости от того, как free () обрабатывает указатель. Если он просто освобождает память и оставляет указатель, указывающий на старое местоположение, код может работать. Если он назначит другое значение указателю, то вы перезапишете другой раздел памяти.
Аланиан

2
printf (строка) не так! Не делай этого. Это открывает ваш код для уязвимости строкового формата, где вы можете свободно читать / записывать напрямую в память через печатные материалы. Если бы я поместил% n /% p в файл и указал указатель на адрес в памяти (в строке из файла), которым я управлял, я мог бы выполнить этот код.
Oxagast

10

readLine() возвращает указатель на локальную переменную, которая вызывает неопределенное поведение.

Обойти можно:

  1. Создайте переменную в функции вызывающего и передайте ее адрес readLine()
  2. Выделите память для lineиспользования malloc()- в этом случаеline будет постоянным
  3. Используйте глобальную переменную, хотя обычно это плохая практика


4

Некоторые вещи не так с примером:

  • Вы забыли добавить \ n в свои printfs. Также сообщения об ошибках должны идти в stderr, т.е.fprintf(stderr, ....
  • (не большой, но) рассмотреть вопрос об использовании, fgetc()а не getc(). getc()это макрос, fgetc()это правильная функция
  • getc()возвращает intтак chдолжен быть объявлен как int. Это важно, так как сравнение с EOFбудет обработано правильно. Некоторые 8-битные наборы символов используют 0xFFв качестве допустимого символа (например, ISO-LATIN-1), и его значение EOFравно -1, 0xFFесли он присваивается a char.
  • Существует потенциальное переполнение буфера в строке

    lineBuffer[count] = '\0';

    Если длина строки ровно 128 символов, countэто 128 в точке, которая будет выполнена.

  • Как уже отмечали другие, lineэто локально объявленный массив. Вы не можете вернуть указатель на него.

  • strncpy(count + 1)скопирует в большинстве count + 1символов , но будет прекращено , если он попадает '\0' Потому что вы установили , lineBuffer[count]чтобы '\0'вы знаете , что никогда не получите count + 1. Однако, если бы он это сделал, он не стал бы завершать '\0', поэтому вам нужно это сделать. Вы часто видите что-то вроде следующего:

    char buffer [BUFFER_SIZE];
    strncpy(buffer, sourceString, BUFFER_SIZE - 1);
    buffer[BUFFER_SIZE - 1] = '\0';
  • если вы malloc()хотите вернуть строку (вместо вашего локального charмассива), ваш тип возврата должен быть char*- отбросить const.


2
void readLine(FILE* file, char* line, int limit)
{
    int i;
    int read;

    read = fread(line, sizeof(char), limit, file);
    line[read] = '\0';

    for(i = 0; i <= read;i++)
    {
        if('\0' == line[i] || '\n' == line[i] || '\r' == line[i])
        {
            line[i] = '\0';
            break;
        }
    }

    if(i != read)
    {
        fseek(file, i - read + 1, SEEK_CUR);
    }
}

что насчет этого?


2

Вот мои несколько часов ... Чтение всего файла построчно.

char * readline(FILE *fp, char *buffer)
{
    int ch;
    int i = 0;
    size_t buff_len = 0;

    buffer = malloc(buff_len + 1);
    if (!buffer) return NULL;  // Out of memory

    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
    {
        buff_len++;
        void *tmp = realloc(buffer, buff_len + 1);
        if (tmp == NULL)
        {
            free(buffer);
            return NULL; // Out of memory
        }
        buffer = tmp;

        buffer[i] = (char) ch;
        i++;
    }
    buffer[i] = '\0';

    // Detect end
    if (ch == EOF && (i == 0 || ferror(fp)))
    {
        free(buffer);
        return NULL;
    }
    return buffer;
}

void lineByline(FILE * file){
char *s;
while ((s = readline(file, 0)) != NULL)
{
    puts(s);
    free(s);
    printf("\n");
}
}

int main()
{
    char *fileName = "input-1.txt";
    FILE* file = fopen(fileName, "r");
    lineByline(file);
    return 0;
}

1
Почему вы используете fgetcвместо fgets?
Theicfire

1
const char *readLine(FILE *file, char* line) {

    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);
    return line;

}


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

обратите внимание, что переменная 'line' объявляется в вызывающей функции и затем передается, поэтому ваша readLineфункция заполняет предварительно определенный буфер и просто возвращает его. Именно так работает большинство библиотек Си.

Есть и другие способы, о которых я знаю:

  • определяя char line[]как статический ( static char line[MAX_LINE_LENGTH] -> он будет хранить свое значение ПОСЛЕ возвращения из функции). -> плохо, функция не реентерабельна, и может возникнуть условие гонки -> если вы вызовете ее дважды из двух потоков, она перезапишет свои результаты
  • malloc()ИНГ полукокса линии [], и освобождая его в вызове функций -> слишком много дорогих mallocс, и, делегируя ответственность освободить буфер другой функции (наиболее элегантное решение заключается в вызове mallocи freeна любых буферов в той же функции)

Кстати, «явное» приведение от char*к const char*излишне.

Кстати, lineBuffer не нужен malloc(), просто определите его char lineBuffer[128], так что вам не нужно его освобождать

Кстати, не используйте «динамические стековые массивы» (определяя массив как char arrayName[some_nonconstant_variable]), если вы точно не знаете, что делаете, это работает только в C99.


1
обратите внимание, что переменная 'line' объявляется в вызывающей функции и затем передается, - тогда вам, вероятно, следовало удалить локальное объявление строки в функции. Кроме того, вам нужно сообщить функции, как долго проходит буфер, и подумать о стратегии обработки строк, которые слишком длинны для буфера, который вы передаете.
JeremyP

1

Вы должны использовать функции ANSI для чтения строки, например. fgets. После вызова вам нужно free () в контексте вызова, например:

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

1

Реализуйте метод для чтения и получения содержимого из файла (input1.txt)

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

void testGetFile() {
    // open file
    FILE *fp = fopen("input1.txt", "r");
    size_t len = 255;
    // need malloc memory for line, if not, segmentation fault error will occurred.
    char *line = malloc(sizeof(char) * len);
    // check if file exist (and you can open it) or not
    if (fp == NULL) {
        printf("can open file input1.txt!");
        return;
    }
    while(fgets(line, len, fp) != NULL) {
        printf("%s\n", line);
    }
    free(line);
}

Надеюсь, это поможет. Удачного кодирования!


0

Вы делаете ошибку, возвращая указатель на автоматическую переменную. Строка переменной размещается в стеке и живет только до тех пор, пока функционирует функция. Вам не разрешено возвращать указатель на него, потому что как только он вернется, память будет отдана в другом месте.

const char* func x(){
    char line[100];
    return (const char*) line; //illegal
}

Чтобы избежать этого, вы либо возвращаете указатель на память, которая находится в куче, например. lineBuffer, и пользователь должен сам вызывать функцию free (), когда с ним покончено. В качестве альтернативы вы можете попросить пользователя передать вам в качестве аргумента адрес памяти, на который нужно записать содержимое строки.


Есть разница между незаконным и неопределенным поведением ^^.
Фонг

0

Я хочу код с нуля, поэтому я сделал это, чтобы прочитать содержание словарного слова построчно.

char temp_str [20]; // вы можете изменить размер буфера в соответствии с вашими требованиями и длиной одной строки в файле.

Примечание. Я инициализирую буфер символом Null каждый раз, когда читаю строку. Эта функция может быть автоматизирована, но поскольку мне нужно подтверждение концепции и я хочу разработать программу Byte By Byte

#include<stdio.h>

int main()
{
int i;
char temp_ch;
FILE *fp=fopen("data.txt","r");
while(temp_ch!=EOF)
{
 i=0;
  char temp_str[20]={'\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0'};
while(temp_ch!='\n')
{
  temp_ch=fgetc(fp);
  temp_str[i]=temp_ch;
  i++;
}
if(temp_ch=='\n')
{
temp_ch=fgetc(fp);
temp_str[i]=temp_ch;
}
printf("%s",temp_str);
}
return 0;
}

Ваша программа будет работать, если ваши скобки будут в нужных местах;) Напримерint main() {
dylnmc

Кстати, вам не нужно указывать все 20 '\ 0'. Вы можете просто написать: codechar temp_str [20] = {'\ 0'}; code c автоматически заполнит каждый слот нулевым терминатором, так как декларации массива работают так: если массив инициализируется с меньшим количеством элементов, чем массив, последний элемент будет заполнять оставшиеся элементы.
аланиан

Я char temp_str[20] = {0}также считаю , что весь массив символов заполняется нулевыми терминаторами.
Чт Ён Тун

0

Мой инструмент с нуля:

FILE *pFile = fopen(your_file_path, "r");
int nbytes = 1024;
char *line = (char *) malloc(nbytes);
char *buf = (char *) malloc(nbytes);

size_t bytes_read;
int linesize = 0;
while (fgets(buf, nbytes, pFile) != NULL) {
    bytes_read = strlen(buf);
    // if line length larger than size of line buffer
    if (linesize + bytes_read > nbytes) {
        char *tmp = line;
        nbytes += nbytes / 2;
        line = (char *) malloc(nbytes);
        memcpy(line, tmp, linesize);
        free(tmp);
    }
    memcpy(line + linesize, buf, bytes_read);
    linesize += bytes_read;

    if (feof(pFile) || buf[bytes_read-1] == '\n') {
        handle_line(line);
        linesize = 0;
        memset(line, '\0', nbytes);
    }
}

free(buf);
free(line);

Почему вы используете кучу (malloc) вместо стека? Кажется, есть простое решение на основе стека, fgetsкоторое можно использовать.
Theicfire

0

Предоставить переносимую и универсальную getdelimфункцию, тест пройден через msvc, clang, gcc.

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

ssize_t
portabl_getdelim(char ** restrict linep,
                 size_t * restrict linecapp,
                 int delimiter,
                 FILE * restrict stream) {
    if (0 == *linep) {
        *linecapp = 8;
        *linep = malloc(*linecapp);
        if (0 == *linep) {
            return EOF;
        }
    }

    ssize_t linelen = 0;
    int c = 0;
    char *p = *linep;

    while (EOF != (c = fgetc(stream))) {
        if (linelen == (ssize_t) *linecapp - 1) {
            *linecapp <<= 1;
            char *p1 = realloc(*linep, *linecapp);
            if (0 == *p1) {
                return EOF;
            }
            p = p1 + linelen;
        }
        *p++ = c;
        linelen++;

        if (delimiter == c) {
            *p = 0;
            return linelen;
        }
    }
    return EOF == c ? EOF : linelen;
}


int
main(int argc, char **argv) {
    const char *filename = "/a/b/c.c";
    FILE *file = fopen(filename, "r");
    if (!file) {
        perror(filename);
        return 1;
    }

    char *line = 0;
    size_t linecap = 0;
    ssize_t linelen;

    while (0 < (linelen = portabl_getdelim(&line, &linecap, '\n', file))) {
        fwrite(line, linelen, 1, stdout);
    }
    if (line) {
        free(line);
    }
    fclose(file);   

    return 0;
}

Зачем это делать, когда fgetsсуществует?
Theicfire

fgets может настроить разделители строк или настроить, что делать с текущими строками?

getdelimпозволяет настраивать разделители. Кроме того, я заметил, что нет ограничения на длину строки - в этом случае вы можете использовать стек с getline. (Оба описаны здесь: man7.org/linux/man-pages/man3/getline.3.html )
theicfire

Вы говорите только о Linux, вопрос о том, как читать строки в C, верно?
南山竹

Это работает для любой стандартной реализации c ( getdelimи getlineбыло стандартизировано в POSIX.1-2008, кто-то еще упоминает на этой странице). fgetsтакже стандартный c, а не специфичный для linux
theicfire
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.