Модераторы: Daevaorn

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Считывание файла неизвестного размера в массив, Проблема с распределением памяти  
:(
    Опции темы
StealtH
Дата 18.5.2007, 18:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 124
Регистрация: 16.9.2004

Репутация: нет
Всего: нет



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

Перерыл весь гугл(и этот форум в том числе) в поисках работающего примера(даже некоторую его зарубежную часть) но ничего толком работающего не нашел, почитал маны и написал сам(код приведен ниже). но программа не работает, на выходе массива нет, мои подозрения падают на 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 
PM MAIL   Вверх
Anikmar
Дата 18.5.2007, 18:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2513
Регистрация: 26.11.2006
Где: Санкт-Петербург

Репутация: 9
Всего: 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:02
PM MAIL ICQ   Вверх
Anikmar
Дата 18.5.2007, 19:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2513
Регистрация: 26.11.2006
Где: Санкт-Петербург

Репутация: 9
Всего: 59



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

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++){
Посмотрите внимательно на условие цикла. Вы перепутали переменые.




PM MAIL ICQ   Вверх
Fazil6
Дата 18.5.2007, 19:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1653
Регистрация: 3.5.2006
Где: Минск

Репутация: 35
Всего: 60



Код

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

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

PM MAIL   Вверх
Anikmar
Дата 18.5.2007, 19:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2513
Регистрация: 26.11.2006
Где: Санкт-Петербург

Репутация: 9
Всего: 59



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

А разве realloc автоматически не копирует блок, если он помещается на новое место?
PM MAIL ICQ   Вверх
Fazil6
Дата 18.5.2007, 19:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1653
Регистрация: 3.5.2006
Где: Минск

Репутация: 35
Всего: 60



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

ну да... погорячился.
PM MAIL   Вверх
StealtH
Дата 18.5.2007, 23:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 124
Регистрация: 16.9.2004

Репутация: нет
Всего: нет



Цитата(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 
PM MAIL   Вверх
Anikmar
Дата 19.5.2007, 08:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 2513
Регистрация: 26.11.2006
Где: Санкт-Петербург

Репутация: 9
Всего: 59



Цитата(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;
........



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

Это сообщение отредактировал(а) Anikmar - 19.5.2007, 08:18
PM MAIL ICQ   Вверх
Dov
Дата 19.5.2007, 10:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


аСинизатор
***


Профиль
Группа: Завсегдатай
Сообщений: 1721
Регистрация: 10.5.2003
Где: Эрец-Исраэль

Репутация: 15
Всего: 88



Цитата(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;
}



--------------------
Тут вечности запах томительный,
И свежие фрукты дешевые, 
А климат у нас – изумительный, 
И только соседи – #уевые. 
                           Игорь Губерман.
PM   Вверх
Dov
Дата 19.5.2007, 11:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


аСинизатор
***


Профиль
Группа: Завсегдатай
Сообщений: 1721
Регистрация: 10.5.2003
Где: Эрец-Исраэль

Репутация: 15
Всего: 88



Хотя, имхо, можно и по-другому. Например, передавать в ф-цию имя файла и адрес двумерного динамического массива, который будет формироваться в ф-ции, а возвращать количество строк этого массива.
Например...
Код
#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;
}




--------------------
Тут вечности запах томительный,
И свежие фрукты дешевые, 
А климат у нас – изумительный, 
И только соседи – #уевые. 
                           Игорь Губерман.
PM   Вверх
Syberex
Дата 23.8.2007, 10:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 208
Регистрация: 15.9.2006
Где: Украина

Репутация: нет
Всего: нет



//открытие текстового файла в массив
Код

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

--------------------
whole power in artificial intelligences
PM MAIL ICQ   Вверх
MAKCim
Дата 23.8.2007, 10:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Воін дZэна
****


Профиль
Группа: Экс. модератор
Сообщений: 5644
Регистрация: 10.12.2005
Где: Менск, РБ

Репутация: 52
Всего: 207



Syberex
нашел же тему  smile 
по вопросу
тут нужен mmap()


--------------------
Ах, у елі, ах, у ёлкі, ах, у елі злыя волкі ©

PM MAIL   Вверх
bsa
Дата 23.8.2007, 10:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 63
Всего: 196



Syberex, функция filelength() нестандартна. Надо пользоваться комбинацией методов класса istream: seekg(0, std::ios_base::end).tellg().
PM   Вверх
akizelokro
Дата 24.8.2007, 15:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Крокодил
**


Профиль
Группа: Участник
Сообщений: 761
Регистрация: 30.7.2007

Репутация: 1
Всего: 5



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

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

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

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



Это сообщение отредактировал(а) akizelokro - 24.8.2007, 15:50


--------------------
a = a + b; b = a - b; a = a - b;
PM MAIL   Вверх
StealtH
Дата 15.9.2007, 20:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 124
Регистрация: 16.9.2004

Репутация: нет
Всего: нет



akizelokro, конечно алгоритм замечательный, за тем маленьким исключением, что размер файла изначально не известен, и он может быть от 2Мб, до 2Гб, если выделять область памяти = размер файла+N байт, то реально можно положить любую машину на лопатки при встрече с громадным файлом. Язык С выбран был не случайно, т.к. подобная задача уже реализована на Perl, но нужно реальное быстродействие и наименьшее потребление ресурсов, путь по которому ты предлагаешь идти - ресурсоемкий, соответственно не подходит :(. Но все равно - спасибо за совет smile
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1058 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.