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)по суті є неоперативним.
aroth

50
Для тих, хто сказав, що ця лінія є специфічною для 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;
}

ADDED (відповідь на подальше запитання в коментарі): 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 ansd, повинен бути звільнений абонентом. Тоді люди, які використовують вашу функцію 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

І, до речі, проблему ти не вирішуєш. ОП описує, що повертається значення його функції зникає. Я не бачу, щоб ви вирішили цю проблему.
hagello

@Hartley Я знаю, що це старіший коментар, але я додаю це, щоб хтось не читав його коментар і намагався звільнити (рядок) у циклі. Пам'ять для рядка виділяється лише один раз до початку циклу, тому вона повинна бути вільною лише один раз після закінчення циклу. Якщо ви спробуєте звільнити лінію всередині циклу, ви отримаєте несподівані результати. Залежно від того, як безкоштовно () обробляє вказівник. Якщо він просто розміщує пам'ять і залишає вказівник, що вказує на старе місце, код може працювати. Якщо він призначить вказівник інше значення, то ви перезапишете інший розділ пам'яті.
alaniane

2
printf (рядок) помиляється! Не роби цього. Це відкриває ваш код до вразливості формату рядків, де ви можете вільно читати / записувати безпосередньо в пам'ять за допомогою друкованих матеріалів. Якби я поклав у файл% n /% p і повернув покажчик назад на адресу в пам'яті (у рядку з файлу), який я контролював, я міг би виконати цей код.
оксагаст

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якщо призначено а 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()ing char line [] та звільнення її у функціях виклику -> занадто багато дорогих mallocs, і, делегуючи відповідальність за звільнення буфера на іншу функцію (найелегантнішим рішенням є виклик mallocта freeбудь-які буфери тієї ж функції)

btw, "явний" кастинг від char*до const char*є зайвим.

btw2, немає необхідності malloc()в LineBuffer, просто визначте його char lineBuffer[128], тому вам не потрібно звільняти його

btw3 не використовують масиви стека динамічного розміру (визначаючи масив як char arrayName[some_nonconstant_variable]), якщо ви точно не знаєте, що ви робите, він працює лише в C99.


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

1

Ви повинні використовувати функції ANSI для читання рядка, наприклад. fgets. Після дзвінка вам потрібно безкоштовно () у контексті виклику, наприклад:

...
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 (), коли він з ним працює. Крім того, ви можете попросити користувача передати вам як аргумент адресу пам'яті, на яку слід записувати вміст рядка на.


Існує різниця між незаконною та невизначеною поведінкою ^^.
Phong

0

Я хочу код від землі 0, тому я зробив це, щоб прочитати зміст слова слова за рядком.

char temp_str [20]; // Ви можете змінити розмір буфера відповідно до ваших вимог. І довжина одного рядка у файлі.

Примітка Я ініціалізував буфер із символом Null кожен раз, коли я читав рядок. Цю функцію можна автоматизувати, але оскільки мені потрібен доказ концепції і хочу створити програму Byte 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.