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': двойно безплатно или повреда (out): 0x0000000000adf270

Прекратено (ядрото е изхвърлено)

Може ли някой да каже защо е тази грешка? :( Или има по-добър начин за четене на този вид файл с данни?


person Kaur    schedule 12.02.2015    source източник
comment
sizeof(rowbuffer) == sizeof( char * )... това вероятно е 4 или 8, в зависимост от хардуера. Тъй като предполагате, че това е размерът на разпределения буфер, вашите предположения са грешни.   -  person DevSolar    schedule 12.02.2015
comment
възможен дубликат на Как да намеря 'sizeof'(a указател, сочещ към масив)?   -  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