fgets не читает полную строку на C

У меня есть файл data.csv, содержащий данные типа float:


0.22,0.33,0.44

0.222,0.333,0.444


Мне нужно прочитать этот файл в двухмерном динамическом массиве. Но я не могу прочитать всю строку с fgets. Не уверен, почему?

Вот мой код на C, который я использовал в Ubuntu:

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

int main(int argc, char *argv[]) {
    FILE *fp;
    float **data;    
    int i,j,rows=2,cols=3;   
    char * token;
    fp=fopen("data.csv","r");
    if(fp==NULL) {
            fprintf(stderr,"Can't open input file");
            exit(1);
    }

    data= malloc(rows * sizeof(float*)); 
    char *rowbuffer=malloc( cols * ( sizeof(float)+sizeof(char) ) );
    i=0;
    while(fgets(rowbuffer,sizeof(rowbuffer),fp) !=NULL) {      
        data[i] = malloc(cols * sizeof(float));      
        j=0;
        printf("\n %s",rowbuffer);
        for (token = strtok(rowbuffer,","); token != NULL; token = strtok(NULL, ",")) {
             data[i][j++] = atof(token);
             /*printf("%s",token);*/
        }
        i++;  
    }
    free(rowbuffer);
    for(i = 0; i < rows; i++)
        free(data[i]);
    free(data);
    fclose(fp);
}

Результат такой:

0.22,0.

33,0.44

0.222,0

��

444

Ошибка в `./test ': двойное освобождение или повреждение (выход): 0x0000000000adf270

Прервано (ядро выгружено)

Кто-нибудь может сказать, почему это ошибка? :( Или есть способ лучше прочитать такой файл данных?


person Kaur    schedule 12.02.2015    source источник
comment
_1 _... это скорее всего 4 или 8, в зависимости от оборудования. Поскольку вы предполагаете размер выделенного буфера, ваши предположения неверны.   -  person DevSolar    schedule 12.02.2015
comment
возможный дубликат Как найти 'sizeof' ( указатель, указывающий на массив)?   -  person Klas Lindbäck    schedule 12.02.2015
comment
Также отступ. Пробелы бесплатны. ;-)   -  person DevSolar    schedule 12.02.2015


Ответы (2)


Ваша проблема с кодированием заключается в следующем:

fgets(rowbuffer,sizeof(rowbuffer),fp)

sizeof(rowbuffer) предоставит вам только размер указателя, а не размер памяти, выделенной для указателя.

Чтобы решить эту проблему, вам необходимо указать правильный размер выделенной памяти [cols * ( sizeof(float)+sizeof(char)] для fgets().

Ваша логическая проблема в:

Вы предположили, что печатное представление значения float займет тот же объем памяти, что и для переменной float. Нет это не правда. В печатном представлении каждая цифра (включая десятичную точку и любые начальные или конечные 0 после запятой) будет занимать по одному байту памяти каждая. Вы должны помнить об этом при выделении памяти для целевого буфера.

person Sourav Ghosh    schedule 12.02.2015
comment
Но тогда размер выделенной памяти не соответствует предполагаемому использованию. - person SukkoPera; 12.02.2015
comment
@SukkoPera Верно. Обновлял свой ответ. :-) - person Sourav Ghosh; 12.02.2015

Здесь одна проблема:

char *rowbuffer=malloc( cols * ( sizeof(float)+sizeof(char) ) );

sizeof(float) - это размер, который float использует в памяти, а не в текстовом представлении. При чтении из файлов вы должны выделить буфер, который будет содержать целую строку в текстовом формате. В вашем случае хорошей ставкой может быть следующее:

int bufsize = cols * (3 + DBL_MANT_DIG - DBL_MIN_EXP + 1) + 1;

(Посмотрите, почему это значение и что вам нужно #include: Какова максимальная длина символов, необходимая для представления любого двойного значения?. Конечный + 1 предназначен для учета символа новой строки, который fgets() считывает и включает в буфер.)

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

Получив это значение, используйте его как в malloc(), так и в fgets():

char *rowbuffer=malloc(bufsize);
i=0;
while(fgets(rowbuffer,bufsize,fp) !=NULL) {
...

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

person SukkoPera    schedule 12.02.2015
comment
Спасибо за ваши комментарии. Я думаю, что память, зарезервированная таким образом с использованием bufsize, будет намного больше, чем используется на самом деле. В моих файлах csv есть две особенности: у меня нет предварительной информации о том, сколько строк и столбцов в них есть - также может быть n тысяч или больше. Во-вторых, точность, используемая в отдельных значениях, может варьироваться, например 0,124 или 0,001204. - person Kaur; 14.02.2015
comment
@Kaur: Ну, это очень сильно зависит от того, как данные организованы в файле, который вы хотите прочитать. Если строки имеют ширину в несколько десятков символов (скажем, 80–100), что я и предполагал в вашем случае, использование строчного буфера обычно доступно, и вам он даже не понадобится после завершения чтения. Если строки могут быть (намного) длиннее и / или если вы не знаете максимальную длину, вам придется прибегнуть к другому методу, который позволяет вам читать по одному значению за раз, например, к методу scanf(), который я предлагал. Вы это видели? - person SukkoPera; 16.02.2015
comment
@Kaur: Насчет разницы в точности, ты не удосужился взглянуть на ссылку, которую я тебе дал? - person SukkoPera; 16.02.2015
comment
Да, сэр, я побеспокоился посмотреть в тот же день. Я реализовал это с помощью fscanf, поскольку имею дело с большими файлами данных, и использование памяти является ограничением. Ваши комментарии действительно были полезны для ясности. Я не уверен, стоит ли мне размещать здесь свое решение или оставить сообщение как есть. - person Kaur; 16.02.2015
comment
Конечно, вы можете написать новый ответ на свой вопрос. Просто не забудьте принять ответ, который сработал для вас. - person SukkoPera; 16.02.2015