Сохранение значений в 2D массив - PullRequest
0 голосов
/ 29 апреля 2020

в соответствии с моей задачей мне нужно прочитать файл, переданный в качестве аргумента командной строки, используя C и сохранить его содержимое (каждый символ) в двумерном массиве, чтобы иметь возможность позже изменить значения массива и сохранить измененное содержимое в другой файл , NVM некоторые пользовательские функции.

Вот пример файла, который мне нужно прочитать:

#,#,#,#,#,#,.,#,.,.,.$
#,.,#,.,.,#,.,#,#,#,#$
#,.,#,.,.,.,.,.,.,#,#$
#,.,#,.,.,#,#,#,#,#,#$
#,.,.,#,.,.,.,.,.,.,#$
#,.,.,.,#,.,#,#,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,.,.,.,.,.,#$
#,#,#,#,#,#,#,#,#,.,#$

Вот что я пробовал:

    int main(int argc, char *argv[]) {
        int startX = 3;
        int startY = 3;
        int endX = 6;
        int endY = 6;
        int count = 0;
        int x = 0;
        int y = 0;
        int fd = open(argv[1], O_RDONLY);
        char ch;

        if (fd == -1) {
            mx_printerr("map does not exist\n");
            exit(-1);
        }

        int targetFile =
            open("path.txt", O_CREAT | O_EXCL | O_WRONLY, S_IWUSR | S_IRUSR);

        while (read(fd, &ch, 1)) {
            if (ch == '\n') {
                x++;
            }
            if (ch != ',') {
                count++;
            }
        }

        fd = open(argv[1], O_RDONLY);
        y = (count - x) / x;
        char **arr;
        arr = malloc(sizeof(char *) * x);
        for (int i = 0; i < x; i++) arr[i] = malloc(y);
        int tempX = 0, tempY = 0, tempCount = 0;
        char tempString[count - x];

    // the loop in question >>>>>


       for (int i = 0; i < 10; i++) {
         for (int j = 0; j < 11; j++) {
          while (read(fd, &ch, 1)) {
            if (ch != ',') {
                arr[i][j] = ch;
                // mx_printchar(arr[i][j]);
             }
           }
         }
       }
      for (int i = 0; i < 10; i++) {
         for (int j = 0; j < 11; j++) {
            mx_printchar(arr[i][j]);
       }
     }
            for (int i = 0; i < x; i++) free(arr[i]);
            free(arr);
            close(fd);
            close(targetFile);
            exit(0);
        }

последний, пока l oop должен сохранять содержимое файла в массив. Однако, когда я пытаюсь вывести содержимое массива на консоль, я получаю некоторые значения мусора: 100 ��pp �� ��8��

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

1 Ответ

1 голос
/ 29 апреля 2020

Вы хорошо начали, но затем пошли на неловкий способ обработки чтения и распределения. Существует несколько способов приблизиться к гибкому чтению любого количества символов и любого количества строк в динамически размещаемом объекте указатель-на-указатель char, который можно индексировать как 2D массив. (часто неправильно называют «динамический массив 2D»). Массив вообще не задействован, у вас есть один указатель на большее количество указателей, и вы выделяете блок памяти для ваших указателей (строк), а затем выделяете отдельный блоки памяти для хранения данных каждой строки и назначения начального адреса для каждого такого блока поочередно одному из указателей.

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

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

Ниже мы будем использовать фиксированный буфер для хранения символов до тех пор, пока не будет найден '\n', обозначающий конец строки (или у вас закончится фиксированное хранилище) а затем динамически распределять память для каждой строки и копировать символы из фиксированного буфера в хранилище строк. Как правило, это работоспособное решение, так как вы будете знать некоторую внешнюю границу максимального числа символов, которая может быть в каждой строке (не экономьте). Буфер 2К - это дешевая защита, даже если вы думаете, что читаете не более 100 символов в строке. (если вы используете встроенную систему с ограниченной памятью, я бы уменьшил буфер до 2X ожидаемого максимального числа символов). Если вы определите постоянную верхнюю границу для фиксированного размера буфера - если вы обнаружите, что вам нужно больше, это простое изменение в одном месте вверху вашего файла.

Как это работает?

Начнем с объявления переменных счетчика для отслеживания количества доступных указателей ( avail), счетчик строк (row), счетчик столбцов (col) и фиксированное количество столбцов, которое мы можем использовать для сравнения с количеством столбцов во всех последующих строках (cols). Объявите свой фиксированный буфер (buf) и указатель на указатель для динамического выделения, а также указатель FILE* для обработки файла, например

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

#define NCHARS 2048     /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {

    size_t  avail = 2,      /* initial no. of available pointers to allocate */
            row = 0,        /* row counter */
            col = 0,        /* column counter */
            cols = 0;       /* fixed no. of columns based on 1st row */
    char buf[NCHARS],       /* temporary buffer to hold characters */
        **arr = NULL;       /* pointer-to-pointer-to char to hold grid */

    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

( note: если аргумент не предоставлен, программа будет читать с stdin по умолчанию)

Далее мы проверяем, что файл открыт для чтения, и мы выделяем начальное avail количество указателей:

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    /* allocate/validate initial avail no. of pointers */
    if (!(arr = malloc (avail * sizeof *arr))) {
        perror ("malloc-arr");
        return 1;
    }

Далее, а не зацикливание while ((c = fgetc(fp)) != EOF), просто непрерывно l oop - это позволит вам обработать '\n' или EOF в пределах l oop и не придется обрабатывать память последней строки отдельно после выхода l oop. Начните с чтения следующего символа из файла и проверки, используете ли вы все свои avail способные указатели (указывая, что вам нужно realloc() больше, прежде чем продолжить):

    while (1) { /* loop continually */
        int c = fgetc(fp);                      /* read each char in file */
        if (row == avail) {                     /* if all pointers used */
            /* realloc 2X no. of pointers using temporary pointer */
            void *tmp = realloc (arr, 2 * avail * sizeof *arr);
            if (!tmp) {                         /* validate reallocation */
                perror ("realloc-arr");
                return 1;                       /* return failure */
            }
            arr = tmp;                          /* assign new block to arr */
            avail *= 2;                         /* update available pointers */
        }

( примечание: всегда realloc() с использованием временного указателя. Когда realloc() дает сбой (не в случае сбоя), он возвращает NULL, а если вы перераспределяете, используя arr = realloc (arr, ..), вы просто перезаписали указатель на текущий блок памяти с помощью NULL вызывает потерю указателя и невозможность free() ранее выделенного блока, что приводит к утечке памяти)

Теперь проверьте, достигли ли вы конца строки, или EOF, а в случае EOF, если ваш счет col равен нулю, вы знаете, что достигли EOF после предыдущего '\n', так что вы может просто сломать l oop в этой точке. В противном случае, если вы достигнете EOF с полным числом столбцов, вы знаете, что в вашем файле отсутствует конец файла POSIX, и вам нужно сохранить последнюю строку символов, например,

        if (c == '\n' || c == EOF) {            /* if end of line or EOF*/
            if (c == EOF && !col)               /* EOF after \n - break */
                break;
            if (!(arr[row] = malloc (col))) {   /* allocate/validate col chars */
                perror ("malloc-arr[row]");
                return 1;
            }
            memcpy (arr[row++], buf, col);      /* copy buf to arr[row], increment */
            if (!cols)                          /* if cols not set */
                cols = col;                     /* set cols to enforce cols per-row */
            if (col != cols) {                  /* validate cols per-row */
                fprintf (stderr, "error: invalid no. of cols - row %zu\n", row);
                return 1;
            }
            if (c == EOF)                       /* break after non-POSIX eof */
                break;
            col = 0;                            /* reset col counter zero */
        }

Если ваш символ не является '\n' или EOF, это просто обычный символ, поэтому добавьте его в буфер, проверьте, есть ли в буфере место для следующего, и продолжайте:

        else {  /* reading in line */
            buf[col++] = c;                     /* add char to buffer */
            if (col == NCHARS) {                /* if buffer full, handle error */
                fputs ("error: line exceeds maximum.\n", stderr);
                return 1;
            }
        }
    }

На этом этапе вы все ваши персонажи хранятся в динамически размещенном объекте, который вы можете индексировать как двумерный массив. (вы также знаете, что это просто хранилище символов, которые не оканчиваются нулем, поэтому вы не можете рассматривать каждую строку как строку). Вы можете добавить нулевой завершающий символ, если хотите, но тогда вы можете просто прочитать каждую строку в buf с помощью fgets() и обрезать завершающий символ новой строки, если он есть. Зависит от ваших требований.

Пример просто закрывает файл (если не читает из stdin), выводит сохраненные символы и освобождает всю выделенную память, например,

    if (fp != stdin)                            /* close file if not stdin */
        fclose (fp);

    for (size_t i = 0; i < row; i++) {          /* loop over rows */
        for (size_t j = 0; j < cols; j++)       /* loop over cols */
            putchar (arr[i][j]);                /* output char */
        putchar ('\n');                         /* tidy up with newline */
        free (arr[i]);                          /* free row */
    }
    free (arr);                                 /* free pointers */
}

(это всю программу, вы можете просто вырезать / вставить части вместе)

Пример входного файла

$ cat dat/gridofchars.txt
#,#,#,#,#,#,.,#,.,.,.$
#,.,#,.,.,#,.,#,#,#,#$
#,.,#,.,.,.,.,.,.,#,#$
#,.,#,.,.,#,#,#,#,#,#$
#,.,.,#,.,.,.,.,.,.,#$
#,.,.,.,#,.,#,#,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,.,.,.,.,.,#$
#,#,#,#,#,#,#,#,#,.,#$

Пример использования / Вывод

$ ./bin/read_dyn_grid dat/gridofchars.txt
#,#,#,#,#,#,.,#,.,.,.$
#,.,#,.,.,#,.,#,#,#,#$
#,.,#,.,.,.,.,.,.,#,#$
#,.,#,.,.,#,#,#,#,#,#$
#,.,.,#,.,.,.,.,.,.,#$
#,.,.,.,#,.,#,#,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,.,.,.,.,.,#$
#,#,#,#,#,#,#,#,#,.,#$

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока памяти, поэтому (2) он может быть освобожден , когда он больше не нужен.

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

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

$ valgrind ./bin/read_dyn_grid dat/gridofchars.txt
==29391== Memcheck, a memory error detector
==29391== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==29391== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==29391== Command: ./bin/read_dyn_grid dat/gridofchars.txt
==29391==
#,#,#,#,#,#,.,#,.,.,.$
#,.,#,.,.,#,.,#,#,#,#$
#,.,#,.,.,.,.,.,.,#,#$
#,.,#,.,.,#,#,#,#,#,#$
#,.,.,#,.,.,.,.,.,.,#$
#,.,.,.,#,.,#,#,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,#,.,.,.,.,#$
#,.,.,.,.,.,.,.,.,.,#$
#,#,#,#,#,#,#,#,#,.,#$
==29391==
==29391== HEAP SUMMARY:
==29391==     in use at exit: 0 bytes in 0 blocks
==29391==   total heap usage: 17 allocs, 17 frees, 6,132 bytes allocated
==29391==
==29391== All heap blocks were freed -- no leaks are possible
==29391==
==29391== For counts of detected and suppressed errors, rerun with: -v
==29391== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...