对于如此复杂的结构,这是一项适度的重大任务。这是一个不太短的 SSCCE(简短、独立、完整的示例)。真的有3个文件被撞成一个:
stderr.h
— 错误报告函数的声明(前 10 行)
serialize.c
— 序列化代码(中间不到 300 行)
stderr.c
— 错误报告功能(底部 40 行)
我不打算解释错误报告功能。就格式化参数而言,它们的工作方式或多或少相似printf()
,但它们写入标准错误,而不是标准输出,并且它们包括程序名称作为前缀,以及源自errno
. 该emalloc()
函数检查内存分配,如果分配失败则报告错误并退出。这种错误处理对于简单的程序来说已经足够了;如果存在内存问题、保存工作或其他任何事情,这对于需要恢复的复杂程序来说是不够的。
在真正的序列化代码中,有 4 组函数,加上main()
编排。
- 用于创建和初始化结构的分配和初始化函数。
- 打印函数以转储结构。
- 导出函数用于序列化数据以进行导出。
- 用于反序列化导入数据的导入函数。
打印功能允许人们查看数据,您可以将输出保存到文件中,并将导出数据与导入数据进行比较,以确保它们相同。
如果您使用结构来描述所有二维数组,则代码会更简单,例如:
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]
起点。
进行序列化的方法不止一种。我选择了一个二维数组,由 N 个一维数组表示,每个一维数组都以size_t
描述一维数组大小的前缀为前缀。这在文件中提供了一些冗余,这意味着有更好的错误检测。简单地输出二维数组的两个维度,后跟行 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 *'
to转换'double **'
,这是有道理的,因为它data
是 double 并且emalloc
正在返回void *
;在开始将其嵌入到我的原始程序之前,我该如何解决这些问题?
- 不要使用 C++ 编译器来编译 C 代码
- 更新到带有 C99 编译器的系统。
或者,因为您可能在 Windows 上并使用 MSVC:
- 使用演员表
double **data = (double **)emalloc(rows * sizeof(*data));
- 在 MSDN 中查找
_snprintf()
等等。snprintf_s()
当我需要知道 MSVC 的功能时,我会通过 Google 使用“site:microsoft.com snprintf”(用于“snprintf”的各种拼写)找到它。
在紧急情况下,使用sprintf()
; 缓冲区的大小足够大,不应该有任何溢出的风险,这是snprintf()
et al 所保护的。
顺便说一句,在我的程序中有一个名为cernel_matrix(double **M1 ,double **M2)
的函数,一个采用两个二维矩阵的函数。我正在传递测试样本和xx
这个函数,有时xx
and xx
,有时test_sample
and test_sample
,它取决于所以我不能制作test_sample
一维;这只是函数的工作方式。否则我会得到这个错误: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
对未初始化的数据感到困惑(正如我即将发布的那样)。但我最终发现了一段编辑不完整的复制粘贴代码。
请注意,如果您发布的代码在尝试导出数据方面做得很好,最好是在尝试导入数据方面做得很好,那么该代码可能已被修复。由于您没有发布任何有价值的代码,因此很难在不产生可测试的东西的情况下生成解决您真正问题的代码。我提供的代码是可测试的。测试可能会更全面——是的,毫无疑问。但是使代码可测试并对其进行测试是学习编程的重要部分。
顺便说一句,对于任何类型(如数组)的可变长度数据,导出过程中的关键点是确保在写入数据(数组)本身之前写入数据(数组)的大小。然后导入过程知道在读回数据(数组)之前要分配多少空间。