Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Считывание файла неизвестного размера в массив


Автор: StealtH 18.5.2007, 18:53
Задача одновременно проста, но оказалась сложной, суть ее такова: 
Есть файл(ы) который нужно прочитать построчно в массив, а потом работать уже с массивом, количество строк в файле - неизвестно. Соответственно массив должен быть динамическим и колчиство его элементов должно меняться при чтении файла.

Перерыл весь гугл(и этот форум в том числе) в поисках работающего примера(даже некоторую его зарубежную часть) но ничего толком работающего не нашел, почитал маны и написал сам(код приведен ниже). но программа не работает, на выходе массива нет, мои подозрения падают на realloc и выделение памяти под массив, но где именно и что я делаю не так не могу понять.

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

Компилятор:
Цитата
gcc version 3.3.3 20040412 (ASPLinux 3.3.3-7)


команда компиляции 
Цитата
gcc -o file2array ./file2array.c


Сразу замечу, что код желательно напистаь на C(не С++), и под *NIX системы.

Код

#include <sys/types.h>
#include <stdio.h>
#include <string.h>

#define STRLEN 512              //максимальная длина строки

    char** array;               //массив, который в теории должен потом растягиваться до нужных размеров



int main(){

    char* filename;             //имя файла
    int i = 0;          //счетчик
    FILE* fd;                   //дескриптор файла
    char str[STRLEN];           //строка, которая читается из файла

    filename = "./filename";       //Имя файла

    fd = fopen(filename, "rt"); //Открываем файл

    while( fgets(str,STRLEN,fd ) ){ //читаем из файла по STRLEN байт

        ++i;                            //Наращиваем счетчик
        if(feof(fd)) break;

        strcat(str, "\0");                      //добавляем нуль-символ в конец строки
        array = ( char**)realloc(array, sizeof(char*));         //растягиваем массив на количество символов из файла
        array[i] = ( char*)malloc(strlen(str)+1);               //подготавливаем память для копирования строки из файла
        array[i] = str;                                         //пишем строку в массив
    }

    printf("%s", array[5]);             //Пытаемся увидеть собственными глазами 5-й элемент массива

    puts("\nEND OF FILE READ\n");   //Для себя пометка, что чтение файла закончилось
    int j = 0;;

    //цикл для проверки значений массива
    for(j=0; i<=j; j++){

        printf("%s\n",array[j]);

    }
}



ЗЫ: Просьба не посылать к манам, ибо уже они мне сняться. smile 

Автор: Anikmar 18.5.2007, 18:59
Не проще ли воспользоваться вектором? Для таких целей - самое то.

Упс..
Не заметил 

Сразу замечу, что код желательно напистаь на C(не С++), и под *NIX системы.

Добавлено через 9 минут и 49 секунд
Этот участок мне не понятен:
Код

        array = ( char**)realloc(array, sizeof(char*));         //растягиваем массив на количество символов из файла
        array[i] = ( char*)malloc(strlen(str)+1);               //подготавливаем память для копирования строки из файла
        array[i] = str;                                         //пишем строку в массив


Получается что весь массив заполнен одним и тем же указателем на str
Чтобы записать строку в массив ее надо посимвольно скопировать, тем же strcpy

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

2.
Считывать построчно, задавая максимально возможный размер строки (как это сделано), но для хранения использовать связанный список

3. Написать что-то вроде STL-овского вектора (если нужен именно Си)

Автор: Anikmar 18.5.2007, 19:39
Посмотрел я код внимательнее и увидел там не один, а несколько багов
Скидываю подправленный вариант:
Код

int main(int argc, char* argv[])
{
    char* filename;             //имя файла
    int i = 0;          //счетчик
    FILE* fd;                   //дескриптор файла
    char str[STRLEN];           //строка, которая читается из файла

    array = NULL;
    filename = "input.txt";       //Имя файла

    fd = fopen(filename, "rt"); //Открываем файл

    while(!feof(fd))
    {
        fgets(str,STRLEN,fd);  //читаем из файла по STRLEN байт
        ++i;                            //Наращиваем счетчик
        array = ( char**)realloc(array,sizeof(char*)*i);         //растягиваем массив на количество символов из файла
        array[i-1] = ( char*)malloc(strlen(str)+1);               //подготавливаем память для копирования строки из файла
        strcpy(array[i-1],str);
    }

    puts("\nEND OF FILE READ\n");   //Для себя пометка, что чтение файла закончилось
    int j = 0;

    //цикл для проверки значений массива
    for(j=0; j<i; j++){

        printf("%s\n",array[j]);

    }
    getch();
}




Какие баги в вашем коде я увидел (и исправил):
1.
array = ( char**)realloc(array, sizeof(char*));
Данная функция, на сколько я помню, увеличивает массив не на указанную величину, а до указанной величины - т.е. надо задавать новый размер массива

2.
array[i] = ( char*)malloc(strlen(str)+1); 
У нас первое значение i будет 1 - потеряется нулевой элемент - так как i инкрементируется до этого

3.
array[i] = str;
Так строки не копируется - этим вы копируете указатель на ваш статический массив

4.
for(j=0; i<=j; j++){
Посмотрите внимательно на условие цикла. Вы перепутали переменые.




Автор: Fazil6 18.5.2007, 19:41
Код

 array = ( char**)realloc(array, sizeof(char*));         //растягиваем массив на количество символов из файла

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

Автор: Anikmar 18.5.2007, 19:46
Цитата(Fazil6 @  18.5.2007,  19:41 Найти цитируемый пост)
здесь не растягивание. Память может выделяться каждый раз в новом месте и для помимо realloc нужно еще копировать содержимое в новое место. Прочитай файл вхолостую чтобы узнать количество строк , выдели array и потом пройди заново считывая в него строки

А разве realloc автоматически не копирует блок, если он помещается на новое место?

Автор: Fazil6 18.5.2007, 19:49
Цитата(Anikmar @  18.5.2007,  19:46 Найти цитируемый пост)
А разве realloc автоматически не копирует блок, если он помещается на новое место?

ну да... погорячился.

Автор: StealtH 18.5.2007, 23:57
Цитата(Anikmar @ 18.5.2007,  19:39)
Посмотрел я код внимательнее и увидел там не один, а несколько багов
Скидываю подправленный вариант:
Код

int main(int argc, char* argv[])
{
    char* filename;             //имя файла
    int i = 0;          //счетчик
    FILE* fd;                   //дескриптор файла
    char str[STRLEN];           //строка, которая читается из файла

    array = NULL;
    filename = "input.txt";       //Имя файла

    fd = fopen(filename, "rt"); //Открываем файл

    while(!feof(fd))
    {
        fgets(str,STRLEN,fd);  //читаем из файла по STRLEN байт
        ++i;                            //Наращиваем счетчик
        array = ( char**)realloc(array,sizeof(char*)*i);         //растягиваем массив на количество символов из файла
        array[i-1] = ( char*)malloc(strlen(str)+1);               //подготавливаем память для копирования строки из файла
        strcpy(array[i-1],str);
    }

    puts("\nEND OF FILE READ\n");   //Для себя пометка, что чтение файла закончилось
    int j = 0;

    //цикл для проверки значений массива
    for(j=0; j<i; j++){

        printf("%s\n",array[j]);

    }
    getch();
}




Какие баги в вашем коде я увидел (и исправил):
1.
array = ( char**)realloc(array, sizeof(char*));
Данная функция, на сколько я помню, увеличивает массив не на указанную величину, а до указанной величины - т.е. надо задавать новый размер массива

2.
array[i] = ( char*)malloc(strlen(str)+1); 
У нас первое значение i будет 1 - потеряется нулевой элемент - так как i инкрементируется до этого

3.
array[i] = str;
Так строки не копируется - этим вы копируете указатель на ваш статический массив

4.
for(j=0; i<=j; j++){
Посмотрите внимательно на условие цикла. Вы перепутали переменые.

 smile 
Громадное спасибо, код реально поправил положение дел, я кстати грешил на инкрементную переменную и связанные с ней моменты , даже делал в цикле проверку на наличие в array(i-1) данных - они там там были в период прохождения цикла по файлу array(i-2) - вызывали Segmentation fial, а по поводу переменных в последнем цикле - это даже от себя такой баги не ожидал, если честно долго смеялся сам над собой когда понял. Цитируемый код заработал сразу, но это по сути небольшая верхушка айсберга, приведенный мною пример - это код одной из функции программы, по-этому если не трудно, господа, подскажите каким образом, вернее сказать как более правильно будет организовать функцию которой передается в качестве аргумента массив и указатель на файл а возращаемым агрументом был бы массив, который содержит строки файла, без "\n". 


Спасибо огромное всем кто ответил! 


ЗЫ: Извиняюсь возможно за некоторое незнание некоторых моментов в С, о я последнюю строчку на С, написал в 99 году, по-этому сейчас спустя 8 лет достаточно сложно восстановить знания, прошу отнестись с пониманием  smile 

Автор: Anikmar 19.5.2007, 08:16
Цитата(StealtH @  18.5.2007,  23:57 Найти цитируемый пост)
как более правильно будет организовать функцию которой передается в качестве аргумента массив и указатель на файл а возращаемым агрументом был бы массив, который содержит строки файла, без "\n". 

Я обратил внимание, что функция gets оставляет в конце строки символ \n. Если речь идет о нем - то наиболее быстрым решением я вижу простая замена его на нулевой символ. 

Например, в вышеприведенном коде, можно его сразу отсечь последний символ, если он '\n'

Код

int LastPos;
    while(!feof(fd))
    {
        fgets(str,STRLEN,fd);  //читаем из файла по STRLEN байт
        LastPos= strlen(str)-1;
        if (LastPos >= 0)
            if (str[LastPos] == '\n') str[LastPos] = 0;
........



А вот на счет передачи массива в функцию:
Если мы не знаем заренее сколько строк в файле, то какой массив мы должны передать? Наоборот - это функция должна возвратить считанный массив.

Автор: Dov 19.5.2007, 10:50
Цитата(Anikmar @  19.5.2007,  08:16 Найти цитируемый пост)
А вот на счет передачи массива в функцию:Если мы не знаем заренее сколько строк в файле, то какой массив мы должны передать? Наоборот - это функция должна возвратить считанный массив.

Согласен с Anikmar`ом.  И указатель на файл можно не передавать. Достаточно передать только имя файла. И потом уже работать с файлом непосредственно в самой ф-ции. 
   Можно ещё передать в ф-цию переменную, которая бы сохраняла количество строк в файле для дальнейшей работы с ними.
Ну, что-нибудь такое...
Код

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

#define STRLEN 512              

char** GetData(char* name, int* size)
{
    FILE*  file;
    char** text = NULL;            
    int    last;
    char   buf[STRLEN];

    *size = 0;

    file  = fopen(name, "rt");
    while(fgets(buf, STRLEN, file) != NULL)
    {
        last = strlen(buf) - 1;
        if(buf[last] == '\n')
            buf[last] = '\0';

        text        = (char**)realloc(text, sizeof(char*) * (*size + 1));
        text[*size] = (char*)malloc(sizeof(char) * (strlen(buf) + 1));
        strcpy(text[(*size)++], buf);        
    }

    fclose(file);

    return text; 
}

int main()
{
    char** data;
    char*  filename;                               
    int    count;  

    filename = "test.txt";                        
    data     =  GetData(filename, &count);

    for(int i = 0; i < count; i++)
        printf("%s\n", data[i]);

    for(i = 0; i < count; i++)
        free(data[i]);
    free(data);

    return 0;
}

Автор: Dov 19.5.2007, 11:20
Хотя, имхо, можно и по-другому. Например, передавать в ф-цию имя файла и адрес двумерного динамического массива, который будет формироваться в ф-ции, а возвращать количество строк этого массива.
Например...
Код
#define STRLEN 512              

int GetData(char* name, char*** text)
{
    FILE*  file;
    int    last;
    char   buf[STRLEN];
    int    size = 0;

    *text  = NULL;
    file   = fopen(name, "rt");
    while(fgets(buf, STRLEN, file) != NULL)
    {
        last = strlen(buf) - 1;
        if(buf[last] == '\n')
            buf[last] = '\0';

        *text       = (char**)realloc(*text, sizeof(char*) * (size + 1));
        (*text)[size] = (char*)malloc(sizeof(char) * (strlen(buf) + 1));
        strcpy((*text)[size++], buf);        
    }

    fclose(file);

    return size; 
}

int main()
{
    char** data = NULL;
    char*  filename;                               
    int    count;  

    filename = "test.txt";                        
    count    = GetData(filename, &data);

    for(int i = 0; i < count; i++)
        printf("%s\n", data[i]);

    for(i = 0; i < count; i++)
        free(data[i]);
    free(data);

    return 0;
}


Автор: Syberex 23.8.2007, 10:03
//открытие текстового файла в массив
Код

char*  FileOpenToMass(char* Path)
  {
     int handle=open(Path,O_CREAT);
     unsigned long LenFile=filelength(handle);
     close (handle);
     fstream in(Path,ios_base::in |ios_base::binary);
     char *pointer=new char[LenFile];
     in.read(pointer,LenFile);
     in.close();
     //delete []  pointer;
     return  pointer;
  };

Автор: MAKCim 23.8.2007, 10:47
Syberex
нашел же тему  smile 
по вопросу
тут нужен mmap()

Автор: bsa 23.8.2007, 10:50
Syberex, функция filelength() нестандартна. Надо пользоваться комбинацией методов класса istream: seekg(0, std::ios_base::end).tellg().

Автор: akizelokro 24.8.2007, 15:42
Пойдем по другому пути. Гонять каждый раз при обращению к диску malloc'и я бы посчитал неэкономичным. И fgets вроде бы "мылит" в массив [any] байтов не any, а до до первого '\n'.
Предположим, что размер файла не важен (< размера оперативки +вирт.память). Ну, 10 Мег, которые можно выделить в памяти единым блоком. Предположим, что (как видно из обсуждения) скорость работы проги всем до фени, но пишем все равно на С. Но при этом я буду делать вид, что скорость работы важна и памяти отжирать хотелось бы поменьше. Поэтому я сведу к минимуму обращений к диску и С++ примочкам, которые удобней, но в ..ого!.. число раз медленней.

тогда я бы воткнул такой алгоритм:
выделил памяти(с размер файла).
привел бы ее к общему знаменателю (char * s) и зачтил бы туда файл.
заменил бы там '\n' на '\0', заодно подсчитав число n замен
выделил бы памяти на  указатели строк [n*sizeof(char *)]
еще бы раз проперся по общему знаменателю и заполнил бы массив указателей строк.

А лет 7 назад я не мог себе позволить выделить памяти 10 Мег под файл и усложнял подобные задачи введением буфера длиной всего 64 Кила. Потому что в DOS памяти 10 Мег не было (ехидно ухмыльнулся)/ smile 

Пятница. Машину на прикол. Всем хороших выходных = с пьянством и развратом! smile 


Автор: StealtH 15.9.2007, 20:29
akizelokro, конечно алгоритм замечательный, за тем маленьким исключением, что размер файла изначально не известен, и он может быть от 2Мб, до 2Гб, если выделять область памяти = размер файла+N байт, то реально можно положить любую машину на лопатки при встрече с громадным файлом. Язык С выбран был не случайно, т.к. подобная задача уже реализована на Perl, но нужно реальное быстродействие и наименьшее потребление ресурсов, путь по которому ты предлагаешь идти - ресурсоемкий, соответственно не подходит :(. Но все равно - спасибо за совет smile

Автор: Skladnoy 16.9.2007, 00:59
Цитата(StealtH @  18.5.2007,  18:53 Найти цитируемый пост)
Есть файл(ы) который нужно прочитать построчно в массив, а потом работать уже с массивом, количество строк в файле - неизвестно. Соответственно массив должен быть динамическим и колчиство его элементов должно меняться при чтении файла.


Цитата(StealtH @  15.9.2007,  20:29 Найти цитируемый пост)
akizelokro, конечно алгоритм замечательный, за тем маленьким исключением, что размер файла изначально не известен, и он может быть от 2Мб, до 2Гб, если выделять область памяти = размер файла+N байт, то реально можно положить любую машину на лопатки при встрече с громадным файлом. Язык С выбран был не случайно, т.к. подобная задача уже реализована на Perl, но нужно реальное быстродействие и наименьшее потребление ресурсов, путь по которому ты предлагаешь идти - ресурсоемкий, соответственно не подходит :(. Но все равно - спасибо за совет 


Так при работе нужно держать в памяти весь файл и работать со _всем_ его содержимым? Или хватит построчной обработки? Если файл в память не помещается - придется его читать по кускам. В лучшем случае хватит построчной обработки, в худшем придется изощрятся. 

Что надо с файлом делать? У меня есть подозрение, что надо не прочесть файл, а что-то более другое.

P.S. может быть уже помянутый mmap спасет смертельно раненого кота?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)