Построчное чтение файла в массив строк размером C - PullRequest
1 голос
/ 02 августа 2020

Я пытаюсь прочитать следующий файл построчно в массив строк, где каждая строка является элементом массива:

AATGC
ATGCC
GCCGT
CGTAC
GTACG
TACGT
ACGTA
CGTAC
GTACG
TACGA
ACGAA

Мой код выглядит следующим образом:

void **get_genome(char *filename) {
    FILE *file = fopen(filename, "r");
    int c;
    int line_count = 0;
    int line_length = 0;
    for (c = getc(file); c != EOF; c = getc(file)) {
        if (c == '\n') line_count++;
        else line_length++;
    }
    line_length /= line_count;
    rewind(file);

    char **genome = calloc(line_length * line_count, sizeof(char));
    for (int i = 0; i < line_count; i++) {
        genome[i] = calloc(line_length, sizeof(char));
        fscanf(file, "%s\n", genome[i]);
    }

    printf("%d lines of %d length\n", line_count, line_length);

    for (int i = 0; i < line_count; i++)
        printf("%s\n", genome[i]);
}

Однако по какой-то причине я получаю мусор для первых 2 элементов массива. Вот мой результат:

`NP��
�NP��
GCCGT
CGTAC
GTACG
TACGT
ACGTA
CGTAC
GTACG
TACGA
ACGAA

Ответы [ 3 ]

2 голосов
/ 02 августа 2020

Кажется, вы полагаете, что все строки имеют одинаковую длину. Если это так, у вас все еще есть проблемы:

  • память для указателей строк выделена неправильно, она должна быть

      char **genome = calloc(line_count, sizeof(char *));
    

или лучше и меньше подвержено ошибкам:

    char **genome = calloc(line_count, sizeof(*genome));
  • память для каждой строки должна быть на один байт длиннее нулевого терминатора.

  • \n - строка формата fscanf() соответствует любой последовательности символов пробела. Это избыточно, поскольку %s все равно пропускает их.

  • безопаснее подсчитывать элементы, разделенные пробелами, чтобы избежать неправильного подсчета элементов, если файл содержит какие-либо пустые символы.

  • вы не закрываете file.

  • вы не возвращаете genome в конце функции

  • вы не проверяете на наличие ошибок.

Вот модифицированная версия:

void **get_genome(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (file == NULL)
        return NULL;
    int line_count = 1;
    int item_count = 0;
    int item_length = -1;
    int length = 0;
    int c;
    while ((c = getc(file)) != EOF) {
        if (isspace(c)) {
            if (length == 0)
                continue;  // ignore subsequent whitespace
            item_count++;
            if (item_length < 0) {
                item_length = length;
            } else
            if (item_length != length) {
                printf("inconsistent item length on line %d\", line_count);
                fclose(file);
                return NULL;
            }
            length = 0;
        } else {   
            length++;
        }
    }
    if (length) {
        printf("line %d truncated\n", line_count);
        fclose(file);
        return NULL;
    }
    rewind(file);

    char **genome = calloc(item_count, sizeof(*genome));
    if (genome == NULL) {
        printf("out of memory\n");
        fclose(file);
        return NULL;
    }
    for (int i = 0; i < item_count; i++) {
        genome[i] = calloc(item_length + 1, sizeof(*genome[i]));
        if (genome[i] == NULL) {
            while (i > 0) {
                free(genome[i]);
            }
            free(genome);
            printf("out of memory\n");
            fclose(file);
            return NULL;
        }
        fscanf(file, "%s", genome[i]);
    }
    fclose(file);

    printf("%d items of %d length on %d lines\n",
           item_count, item_length, line_count);

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

    return genome;
}
1 голос
/ 02 августа 2020
 char **genome = calloc(line_length * line_count, sizeof(char));

должно быть

char **genome = calloc(line_count, sizeof(char*));

или более «безопасным»

char **genome = calloc(line_count, sizeof(*genome));

в случае изменения типа генома

иначе выделенный блок, если он недостаточно длинный, если вы находитесь в 64b, потому что line_count равно 5, а не 8, поэтому вы пишете из него с неопределенным поведением

Вам также необходимо вернуть геном в конце функции

Также можно было не подсчитывать количество строк и использовать realloc для увеличения вашего массива при чтении файла

0 голосов
/ 02 августа 2020

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

Вот немного другая версия функции:

char **get_genome(char *filename, size_t *line_count) {
    FILE *file = fopen(filename, "r");
    int c;
    size_t line_length = 0;
    char **genome = NULL, **tmp;

    *line_count = 0;
    if(file)
    {
        while(1)
        {
            c = getc(file);
            if( c == EOF || c == '\n') break;
            line_length++;
        }    
        rewind(file);

        while(1)
        {
            char *line = malloc(line_length + 1);
            if(line)
            {
                if(!fgets(line, line_length + 1, file))
                {
                    free(line);
                    break;
                }
                line[line_length] = 0;
                tmp = realloc(genome, (*line_count + 1) * sizeof(*genome));
                if(tmp)
                {
                    genome = tmp;
                    genome[*line_count] = line;
                    *line_count += 1;
                }
                else
                {
                    // do some memory free magic
                }
            }
        }
        fclose(file);
    }
    return genome;
}
...