Мне нужно прочитать матрицу из файла, размер матрицы которого нам неизвестен.

у меня такая структура

struct Data {
    int ID;
double test_sample[2065][1];
int XX_row;
int XX_col
double **XX;                        //size=[2065][changing]
double **alpha_new;                 //size=[changing][1]
int alpha_new row;
int alpha_new_col;
double t3;
double kernel_par;

}person[20];

Я написал эту структуру для каждого человека (для 20 человек) в 20 файлов, используя fwrite:

fwrite(&person, sizeof( struct Data ), 1,Ptr );

Теперь у меня есть 20 файлов в двоичном виде. Каждый файл включает эти переменные для одного человека. Все в порядке на данный момент.

Проблема: я не могу прочитать файл и присвоить его отметке, потому что в каждом файле размерность XX и alpha_new Matrix разные. (в файле [2065][8],некоторые из них[2065][12])

Мне нужно прочитать эти переменные с помощью fread (или другого) и ввести в программу распознавания лиц... Есть ли способ прочитать переменные по отдельности в файле или мне также следует изменить метод записи?

Я не знаю, как записать все матрицы переменных в один файл без использования структуры!

Я надеюсь, что смогу объяснить свою проблему здесь, извините за мой плохой английский, я жду вашей помощи, чтобы закончить мой окончательный проект на c; Я использую Visual Studio 2012


person SadViolinMan    schedule 30.11.2012    source источник
comment
Итак, у вас проблемы с сериализацией. Есть несколько библиотек (например, tpl, предоставляющая эту функцию). Если вы хотите сделать это самостоятельно, вы должны сохранить размер вашего массива в поле структуры, чтобы вы могли определить, сколько данных принадлежит массиву.   -  person janisz    schedule 01.12.2012
comment
Добро пожаловать в Stack Overflow. Ваш fwrite() вызов не принес много пользы; ваши данные полностью зажарены в ваших 20 файлах (будут неполными). Объявление test_sample выглядит так, как будто вы пытаетесь использовать «взлом структуры» (иначе зачем размерность [1]?), но вы не можете сделать это со структурами в массиве. Вы не должны выписывать указатели; они имеют смысл только в программе, которая записывает, но не в программе, которая читает. Как говорит @janisz, вам нужно внимательно изучить сериализацию.   -  person Jonathan Leffler    schedule 01.12.2012
comment
Есть ли причина, по которой вы не можете вставить нужные размеры в начало файла, чтобы их можно было обнаружить во время загрузки?   -  person EvilTeach    schedule 02.12.2012


Ответы (1)


Для такой сложной структуры это довольно серьезное мероприятие. Вот не очень короткий SSCCE (Короткий, самодостаточный, полный пример). Там действительно 3 файла влепили в один:

  • stderr.h — объявления функций сообщения об ошибках (верхние 10 строк)
  • serialize.c — код сериализации (чуть менее 300 строк между ними)
  • stderr.c — функции сообщения об ошибках (нижние 40 строк)

Я не собираюсь объяснять функции сообщения об ошибках. Они работают более или менее как printf() в том, что касается аргументов форматирования, но они записывают стандартную ошибку, а не стандартный вывод, и включают имя программы в качестве префикса и ошибку, полученную из errno. Функция emalloc() проверяет выделение памяти, сообщает об ошибке и завершает работу, если выделение не удалось. Такая обработка ошибок подходит для простых программ; это не подходит для сложных программ, которые необходимо восстановить, если есть проблема с памятью, сохранение работы или что-то еще.

В реальном коде сериализации есть 4 группы функций плюс main() для оркестровки.

  1. Функции распределения и инициализации для создания и инициализации структур.
  2. Функции печати для вывода структур.
  3. Экспортируйте функции для сериализации данных для экспорта.
  4. Функции импорта для десериализации данных для импорта.

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

Код был бы проще, если бы вы использовали структуру для описания всех ваших 2D-массивов, например:

typedef struct Array_2D
{
    double **data;
    size_t   nrows;
    size_t   ncols;
} Array_2D;

Затем вы просто вставляете 3 из них в свой struct Data:

struct Data
{
    int       ID;
    double    t3;
    double    kernel_par;
    Array_2D  test_sample;
    Array_2D  XX;
    Array_2D  alpha_new;
};

Мне правда непонятно, в чем преимущество double test_sample[2065][1]; по сравнению с double test_sample[2065];. Я замечу, что это делает код более сложным, чем он был бы в противном случае. В итоге я рассматриваю его как обычный одномерный массив double, используя &data->test_sample[0][0] в качестве отправной точки.

Существует несколько способов сериализации. Я решил, что 2D-массив двойников будет представлен N 1D-массивами, и каждый 1D-массив имеет префикс size_t, описывающий размер 1D-массива. Это дает некоторую избыточность в файлах, что означает немного лучшее обнаружение ошибок. Можно было бы просто вывести два измерения двумерного массива, за которыми следуют значения rows x cols. Действительно, в какой-то момент у меня был код импорта, предполагающий, что в то время как код экспорта использует другую технику — это не способствовало счастливой среде выполнения, когда числа были неправильно поняты, и я получал вывод отладки и такие ошибки, как:

test_sample: 2.470328e-323, 1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00
2D array size 4617315517961601024 x 5 = 4639833516098453504
serialize(46983) malloc: *** mmap(size=45035996273704960) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
./serialize: Out of memory (12: Cannot allocate memory)

Это много памяти ... 2.470328e-323 тоже был симптомом проблемы. (Так что нет, я не понял все правильно при первом запуске кода.)

Я провел большую часть тестирования с SAMPLE_SIZE на 5 и NUM_PERSON на 3.

serialize.c

/* stderr.h */
#ifndef STDERR_H_INCLUDED
#define STDERR_H_INCLUDED

static void err_setarg0(char const *argv0);
static void err_sysexit(char const *fmt, ...);
static void err_syswarn(char const *fmt, ...);

#endif /* STDERR_H_INCLUDED */

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

enum { SAMPLE_SIZE = 20 }; /* 2065 in original */
enum { NUM_PERSON  = 10 }; /*   20 in original */

struct Data
{
    int ID;
    double test_sample[SAMPLE_SIZE][1]; //Why?
    size_t XX_row;
    size_t XX_col;
    double **XX;                        //size=[SAMPLE_SIZE][changing]
    double **alpha_new;                 //size=[changing][1]
    size_t alpha_new_row;
    size_t alpha_new_col;
    double t3;
    double kernel_par;
} person[NUM_PERSON];

typedef struct Data Data;

static void *emalloc(size_t nbytes)
{
    void *space = malloc(nbytes);
    if (space == 0)
        err_sysexit("Out of memory");
    return space;
}

static void free_data(Data *data)
{
    for (size_t i = 0; i < data->XX_row; i++)
        free(data->XX[i]);
    free(data->XX);

    for (size_t i = 0; i < data->alpha_new_row; i++)
        free(data->alpha_new[i]);
    free(data->alpha_new);

    data->ID = 0;
    data->t3 = 0.0;
    data->kernel_par = 0.0;
    data->XX = 0;
    data->XX_row = 0;
    data->XX_col = 0;
    data->alpha_new = 0;
    data->alpha_new_row = 0;
    data->alpha_new_col = 0;
}

static void free_array(Data *data, size_t nentries)
{
    for (size_t i = 0; i < nentries; i++)
        free_data(&data[i]);
}

static double **alloc_2D_double(size_t rows, size_t cols)
{
    double **data = emalloc(rows * sizeof(*data));
    for (size_t i = 0; i < rows; i++)
    {
        data[i] = emalloc(cols * sizeof(*data[i]));
    }
    return data;
}

static void populate_data(Data *data, size_t entry_num)
{
    /* entry_num serves as 'changing' size */
    data->ID = entry_num;
    data->t3 = entry_num * SAMPLE_SIZE;
    data->kernel_par = (1.0 * SAMPLE_SIZE) / entry_num;

    for (size_t i = 0; i < SAMPLE_SIZE; i++)
        data->test_sample[i][0] = i + entry_num;

    data->XX_row = SAMPLE_SIZE;
    data->XX_col = entry_num;
    data->XX = alloc_2D_double(data->XX_row, data->XX_col);

    for (size_t i = 0; i < data->XX_row; i++)
    {
        for (size_t j = 0; j < data->XX_col; j++)
            data->XX[i][j] = i * data->XX_col + j;
    }

    data->alpha_new_row = entry_num;
    data->alpha_new_col = 1;
    data->alpha_new = alloc_2D_double(data->alpha_new_row, data->alpha_new_col);

    for (size_t i = 0; i < data->alpha_new_row; i++)
    {
        for (size_t j = 0; j < data->alpha_new_col; j++)
            data->alpha_new[i][j] = i * data->alpha_new_col + j;
    }
}

static void populate_array(Data *data, size_t nentries)
{
    for (size_t i = 0; i < nentries; i++)
        populate_data(&data[i], i+1);
}

static void print_1D_double(FILE *fp, char const *tag, double const *values, size_t nvalues)
{
    char const *pad = "";
    fprintf(fp, "%s: ", tag);
    for (size_t i = 0; i < nvalues; i++)
    {
        fprintf(fp, "%s%e", pad, values[i]);
        pad = ", ";
    }
    putc('\n', fp);
}

static void print_2D_double(FILE *fp, char const *tag, double **values, size_t nrows, size_t ncols)
{
    fprintf(fp, "2D array %s[%zd][%zd]\n", tag, nrows, ncols);
    for (size_t i = 0; i < nrows; i++)
    {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "%s[%zd]", tag, i);
        print_1D_double(fp, buffer, values[i], ncols);
    }
}

static void print_data(FILE *fp, char const *tag, const Data *data)
{
    fprintf(fp, "Data: %s\n", tag);
    fprintf(fp, "ID = %d; t3 = %e; kernel_par = %e\n", data->ID, data->t3, data->kernel_par);
    print_1D_double(fp, "test_sample", &data->test_sample[0][0], sizeof(data->test_sample)/sizeof(data->test_sample[0][0]));
    print_2D_double(fp, "XX", data->XX, data->XX_row, data->XX_col);
    print_2D_double(fp, "Alpha New", data->alpha_new, data->alpha_new_row, data->alpha_new_col);
}

static void print_array(FILE *fp, char const *tag, const Data *data, size_t nentries)
{
    fprintf(fp, "Array: %s\n", tag);
    fprintf(fp, "Size: %zd\n", nentries);
    for (size_t i = 0; i < nentries; i++)
    {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "Row %zd", i);
        print_data(fp, buffer, &data[i]);
    }
    fprintf(fp, "End Array: %s\n\n", tag);
}

static void set_file_name(char *buffer, size_t buflen, size_t i)
{
    snprintf(buffer, buflen, "exp_data.%.3zd.exp", i);
}

static void export_1D_double(FILE *fp, double *data, size_t ncols)
{
    if (fwrite(&ncols, sizeof(ncols), 1, fp) != 1)
        err_sysexit("Failed to write number of columns");
    if (fwrite(data, sizeof(double), ncols, fp) != ncols)
        err_sysexit("Failed to write array of %zd doubles", ncols);
}

static void export_2D_double(FILE *fp, double **data, size_t nrows, size_t ncols)
{
    if (fwrite(&nrows, sizeof(nrows), 1, fp) != 1)
        err_sysexit("Failed to write number of rows");
    if (fwrite(&ncols, sizeof(ncols), 1, fp) != 1)
        err_sysexit("Failed to write number of columns");
    for (size_t i = 0; i < nrows; i++)
        export_1D_double(fp, data[i], ncols);
}

static void export_int(FILE *fp, int value)
{
    if (fwrite(&value, sizeof(value), 1, fp) != 1)
        err_sysexit("Failed to write int to file");
}

static void export_double(FILE *fp, double value)
{
    if (fwrite(&value, sizeof(value), 1, fp) != 1)
        err_sysexit("Failed to write double to file");
}

static void export_data(FILE *fp, Data *data)
{
    export_int(fp, data->ID);
    export_double(fp, data->t3);
    export_double(fp, data->kernel_par);
    export_1D_double(fp, &data->test_sample[0][0], sizeof(data->test_sample)/sizeof(data->test_sample[0]));
    export_2D_double(fp, data->XX, data->XX_row, data->XX_col);
    export_2D_double(fp, data->alpha_new, data->alpha_new_row, data->alpha_new_col);
}

static void export_array(Data *data, size_t nentries)
{
    for (size_t i = 0; i < nentries; i++)
    {
        char filename[30];
        set_file_name(filename, sizeof(filename), i);
        FILE *fp = fopen(filename, "w");
        if (fp == 0)
            err_sysexit("Failed to open file %s for writing", filename);
        printf("Export %zd to %s\n", i, filename);
        export_data(fp, &data[i]);
        fclose(fp);
    }
}

static int import_int(FILE *fp)
{
    int value;
    if (fread(&value, sizeof(value), 1, fp) != 1)
        err_sysexit("Failed to read int");
    return value;
}

static double import_double(FILE *fp)
{
    double value;
    if (fread(&value, sizeof(value), 1, fp) != 1)
        err_sysexit("Failed to read int");
    return value;
}

static size_t import_size_t(FILE *fp)
{
    size_t value;
    if (fread(&value, sizeof(value), 1, fp) != 1)
        err_sysexit("Failed to read size_t");
    return value;
}

static void import_1D_double(FILE *fp, double *data, size_t nvalues)
{
    size_t size = import_size_t(fp);
    if (size != nvalues)
        err_sysexit("Size mismatch (wanted %zd, actual %zd)\n", nvalues, size);
    if (fread(data, sizeof(data[0]), nvalues, fp) != nvalues)
        err_sysexit("Failed to read %zd doubles");
}

static void import_2D_double(FILE *fp, double ***data, size_t *nrows, size_t *ncols)
{
    *nrows = import_size_t(fp);
    *ncols = import_size_t(fp);
    *data  = alloc_2D_double(*nrows, *ncols);
    for (size_t i = 0; i < *nrows; i++)
        import_1D_double(fp, (*data)[i], *ncols);
}

static void import_data(FILE *fp, Data *data)
{
    data->ID = import_int(fp);
    data->t3 = import_double(fp);
    data->kernel_par = import_double(fp);

    import_1D_double(fp, &data->test_sample[0][0], sizeof(data->test_sample)/sizeof(data->test_sample[0][0]));
    import_2D_double(fp, &data->XX, &data->XX_row, &data->XX_col);
    import_2D_double(fp, &data->alpha_new, &data->alpha_new_row, &data->alpha_new_col);
}

static void import_array(Data *data, size_t nentries)
{
    for (size_t i = 0; i < nentries; i++)
    {
        char filename[30];
        set_file_name(filename, sizeof(filename), i);
        FILE *fp = fopen(filename, "r");
        if (fp == 0)
            err_sysexit("Failed to open file %s for reading", filename);
        printf("Import %zd from %s\n", i, filename);
        import_data(fp, &data[i]);
        fclose(fp);
    }
}

int main(int argc, char **argv)
{
    err_setarg0(argv[0]);
    if (argc != 1)
        err_syswarn("Ignoring %d irrelevant arguments", argc-1);
    populate_array(person, NUM_PERSON);
    print_array(stdout, "Freshly populated", person, NUM_PERSON);
    export_array(person, NUM_PERSON);
    printf("\n\nEXPORT COMPLETE\n\n");
    free_array(person, NUM_PERSON);
    import_array(person, NUM_PERSON);
    printf("\n\nIMPORT COMPLETE\n\n");
    print_array(stdout, "Freshly imported", person, NUM_PERSON);
    free_array(person, NUM_PERSON);
    return(0);
}

/* stderr.c */
/*#include "stderr.h"*/
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>

static char const *arg0 = "<undefined>";

static void err_setarg0(char const *argv0)
{
    arg0 = argv0;
}

static void err_vsyswarn(char const *fmt, va_list args)
{
    int errnum = errno;
    fprintf(stderr, "%s: ", arg0);
    vfprintf(stderr, fmt, args);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    putc('\n', stderr);
}

static void err_syswarn(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    err_vsyswarn(fmt, args);
    va_end(args);
}

static void err_sysexit(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    err_vsyswarn(fmt, args);
    va_end(args);
    exit(1);
}

При запуске под valgrind он получил чистую справку о здоровье без утечки памяти. И потребовалось более одного прохода, прежде чем я тоже смог с уверенностью сказать это (valgrind обнаружил ошибку, которую не заметили, взглянув на результаты, хотя она была очевидна после обнаружения).


Ответы на вопросы в комментариях

Во всяком случае, вот несколько проблем, возникающих при выполнении кода.

Первый 'snprintf': identifier not found

Второй находится в строке "double **data = emalloc(rows * sizeof(*data));", он говорит, что не может преобразовать из 'void *' в 'double **', и это имеет смысл, потому что data является двойным, а emalloc возвращает void *; как я могу решить эти проблемы, прежде чем начать встраивать это в свою исходную программу?

  1. Не используйте компилятор C++ для компиляции кода C
  2. Обновление до системы с компилятором C99.

Или, поскольку вы, вероятно, работаете в Windows и используете MSVC:

  1. Используйте гипс double **data = (double **)emalloc(rows * sizeof(*data));
  2. Найдите _snprintf() и snprintf_s() и так далее в MSDN. Я нахожу его через Google с помощью «site: microsoft.com snprintf» (для различных вариантов написания «snprintf»), когда мне нужно знать, что делает MSVC.

В экстренных случаях используйте sprintf(); размер буфера достаточно велик, чтобы не было риска переполнения, от которого защищают snprintf() и др.


Кстати, в моей программе есть функция cernel_matrix(double **M1 ,double **M2), функция, принимающая две двумерные матрицы. Я передаю этой функции тестовый образец и xx, иногда xx и xx, иногда test_sample и test_sample, в зависимости от того, что я не могу сделать test_sample 1-мерным; это просто способ работы функции. В противном случае я получу эту ошибку: cannot convert from 'double*' to 'double **'. Надеюсь, я объяснил, почему тестовая выборка не может быть одномерной.

  1. Функция cernel_matrix() не сообщает, насколько велики матрицы, поэтому я не знаю, как она может работать надежно.
  2. Я не уверен, что переход от test_sample к cernel_matrix безопасен; значение double matrix[][1] не преобразуется в double **. Так что я не уверен, что понимаю, почему test_sample такая матрица.

Я собрал для этого микро-тест-кейс:

extern void cernel_matrix(double **M1, double **M2);

extern void m(void);

void m(void)
{
    double **m0;
    double *m1[13];
    double m2[234][1];

    cernel_matrix(m0, m1);
    cernel_matrix(m1, m2);
}

Компилятор сказал мне:

x.c: In function ‘m’:
x.c:12:5: warning: passing argument 2 of ‘cernel_matrix’ from incompatible pointer type [enabled by default]
x.c:1:13: note: expected ‘double **’ but argument is of type ‘double (*)[1]’
x.c:11:18: warning: ‘m0’ is used uninitialized in this function [-Wuninitialized]

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


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

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

Поскольку вам нужно понять код, чтобы представить его учителям, вам, вероятно, потребуется выполнить некоторые упражнения по программированию. Обратите внимание, что одной из первых вещей, которые я сделал, было сокращение задачи до игрушечного размера (вместо 2065 я использовал 5, 10 или 20). Вы должны сделать то же самое. Начните со структуры, которая содержит только элементы фиксированного размера — id, t3, kernel_par и test_sample. Сделайте так, чтобы вы могли инициализировать, экспортировать и импортировать это. Вы можете импортировать в переменную, отличную от той, которую вы экспортируете, а затем выполнить сравнение двух переменных. Вы могли даже опустить test_sample в первой версии.

Когда у вас все получится, добавьте один из ваших массивов и элементы его измерения. Теперь заставьте это работать (с размером 4x5 или подобным). Затем добавьте другой массив (это должно быть тривиально). Когда вы это сделаете, вы должны увидеть, что делают различные функции в примере, который я привел, и почему они здесь. Все они «необходимы» на каком-то уровне. Как я упоминал в своих комментариях, мне потребовалось несколько (слишком много) попыток, чтобы сделать это правильно. Я компилировал со строгими параметрами предупреждений, но valgrind все еще трепался о неинициализированных данных (как я собирался опубликовать). Но в конце концов я заметил не полностью отредактированный фрагмент кода, который копировал и вставлял.

Обратите внимание, что если бы вы опубликовали код, который выполнял разумную работу по попытке экспорта данных и, желательно, разумную работу по попытке импортировать данные, то этот код можно было бы исправить. Поскольку вы не опубликовали никакого кода, который бы стоил того, было сложно создать код, который решал бы вашу настоящую проблему, не создав чего-то поддающегося тестированию. Код, который я предоставил, можно проверить. Тестирование могло бы быть более комплексным — да, несомненно. Но создание тестируемого кода и его тестирование — важная часть обучения программированию.

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

person Jonathan Leffler    schedule 01.12.2012
comment
как я могу решить эту проблему 1) в строке double **data = emalloc(rows * sizeof(*data)); он говорит, что не может преобразовать из «void» в «double *» 2) «snprintf»: идентификатор не найден - person SadViolinMan; 02.12.2012
comment
(1) Не используйте компилятор C++ для компиляции кода C и (2) обновляйте систему до компилятора C99. Или, поскольку вы, вероятно, работаете в Windows и используете MSVC, (1) используйте преобразование double **data = (double **)emalloc(rows * sizeof(*data)); и (2) ищите _snprintf() и snprintf_s() и т. д. в MSDN. Я нахожу его через Google с помощью «site: microsoft.com snprintf» (для различных вариантов написания «snprintf»), когда мне нужно знать, что делает MSVC. В экстренных случаях используйте sprintf(); размер буфера достаточно велик, чтобы не было риска переполнения, от которого защищают snprintf() и др. - person Jonathan Leffler; 02.12.2012
comment
я изменил свою функцию, чтобы она работала так: build_kernel_matrix(double *x,double *t); теперь он должен быть более стабильным, я изменил test_sample на 1 вектор измерения, и чтобы передать XX вместо передачи 2 измерений, я передаю его столбцы в цикле, так что в каждом цикле только один столбец передается в функцию.... просто я не сделал поймите size_t и static void, что означает статика! и вместо строки size_t нельзя использовать строку int, int col? в основном они целые - person SadViolinMan; 03.12.2012
comment
Да, вы можете использовать int вместо size_t. static означает, что функция не видна за пределами этого исходного файла. Поскольку я компилирую с -Wmissing-prototypes, функция либо нуждается в прототипе в начале файла, прежде чем она будет определена, либо она должна быть статической; Обычно я делаю вещи статическими до тех пор, пока не узнаю, что к ним будут обращаться из другого исходного файла (и если код представляет собой один исходный файл, это означает, что все, кроме main(), будет статическим). - person Jonathan Leffler; 03.12.2012
comment
прошло некоторое время, но, наконец, я закончил свой проект, используя ваши коды сериализации .. действительно большое вам спасибо, вы помогаете ребятам, которых вы даже не знаете, я получил помощь от этого сайта даже больше, чем мои учителя !!! :) - person SadViolinMan; 12.12.2012