Для такой сложной структуры это довольно серьезное мероприятие. Вот не очень короткий SSCCE (Короткий, самодостаточный, полный пример). Там действительно 3 файла влепили в один:
stderr.h
— объявления функций сообщения об ошибках (верхние 10 строк)
serialize.c
— код сериализации (чуть менее 300 строк между ними)
stderr.c
— функции сообщения об ошибках (нижние 40 строк)
Я не собираюсь объяснять функции сообщения об ошибках. Они работают более или менее как printf()
в том, что касается аргументов форматирования, но они записывают стандартную ошибку, а не стандартный вывод, и включают имя программы в качестве префикса и ошибку, полученную из errno
. Функция emalloc()
проверяет выделение памяти, сообщает об ошибке и завершает работу, если выделение не удалось. Такая обработка ошибок подходит для простых программ; это не подходит для сложных программ, которые необходимо восстановить, если есть проблема с памятью, сохранение работы или что-то еще.
В реальном коде сериализации есть 4 группы функций плюс main()
для оркестровки.
- Функции распределения и инициализации для создания и инициализации структур.
- Функции печати для вывода структур.
- Экспортируйте функции для сериализации данных для экспорта.
- Функции импорта для десериализации данных для импорта.
Функции печати позволяют человеку видеть данные, и вы можете сохранить вывод в файл и сравнить данные экспорта с данными импорта, чтобы убедиться, что они совпадают.
Код был бы проще, если бы вы использовали структуру для описания всех ваших 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 *
; как я могу решить эти проблемы, прежде чем начать встраивать это в свою исходную программу?
- Не используйте компилятор C++ для компиляции кода C
- Обновление до системы с компилятором C99.
Или, поскольку вы, вероятно, работаете в Windows и используете MSVC:
- Используйте гипс
double **data = (double **)emalloc(rows * sizeof(*data));
- Найдите
_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 **'
. Надеюсь, я объяснил, почему тестовая выборка не может быть одномерной.
- Функция
cernel_matrix()
не сообщает, насколько велики матрицы, поэтому я не знаю, как она может работать надежно.
- Я не уверен, что переход от
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
fwrite()
вызов не принес много пользы; ваши данные полностью зажарены в ваших 20 файлах (будут неполными). Объявлениеtest_sample
выглядит так, как будто вы пытаетесь использовать «взлом структуры» (иначе зачем размерность[1]
?), но вы не можете сделать это со структурами в массиве. Вы не должны выписывать указатели; они имеют смысл только в программе, которая записывает, но не в программе, которая читает. Как говорит @janisz, вам нужно внимательно изучить сериализацию. - person Jonathan Leffler   schedule 01.12.2012