このような複雑な構造の場合、それは適度に大きな仕事です。これはそれほど短くないSSCCE(短い、自己完結型、完全な例)です。実際には3つのファイルが1つにまとめられています。
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
使用することにより、通常の1D配列として扱うことになります。&data->test_sample[0][0]
シリアル化を行う方法は複数あります。N個の1D配列で表されるdoubleの2D配列を選択しました。各1D配列の前にsize_t
は、1D配列のサイズを説明するものが付いています。これにより、ファイルにある程度の冗長性が与えられます。つまり、エラー検出がわずかに向上します。2D配列の2次元を出力し、その後に行x列の値を出力するだけで実行できます。確かに、ある時点で、エクスポートコードが他の手法を使用していると仮定して、インポートコードがありました。これは、数値が誤解され、デバッグ出力と次のようなエラーが発生したときに、実行時間を満足させることができませんでした。
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
、それはメモリリークのないきれいな健康法案を与えられました。そして、私が安全にそれを言うことができるようになるまでに、1回以上のパスがvalgrind
必要でした(一度検出されると明らかでしたが、結果を目で確認することができなかったというバグが現れました)。
コメントの質問への回答
とにかく、ここにコードの実行中に発生するいくつかの問題があります。
最初のものは'snprintf': identifier not found
2つ目は、からに"double **data = emalloc(rows * sizeof(*data));"
変換できないと言っている行にあり、はdoubleであり、を返しているため、意味があります。これを元のプログラムに埋め込み始める前に、どうすればこれらの問題を解決できますか?'void *'
'double **'
data
emalloc
void *
- CコードのコンパイルにC++コンパイラを使用しないでください
- C99コンパイラを搭載したシステムにアップデートします。
または、おそらくWindowsを使用していて、MSVCを使用しているため:
- キャストを使用する
double **data = (double **)emalloc(rows * sizeof(*data));
- MSDN
_snprintf()
でsnprintf_s()
検索などを行います。MSVCの機能を知る必要がある場合は、Googleで「site:microsoft.comsnprintf」(「snprintf」のさまざまなスペル用)を使用して見つけます。
緊急の場合は、sprintf()
;を使用してください。バッファのサイズは十分に大きいので、オーバーフローのリスクはありません。これは、snprintf()
他の人が防ぐものです。
ちなみに、私のプログラムには、cernel_matrix(double **M1 ,double **M2)
2つの2次元行列をとる関数という関数があります。私はテストサンプルに合格しておりxx
、この関数に、時々xx
、そしてxx
、時々test_sample
、test_sample
それに依存しているので、test_sample
1次元を作成することはできません。それは関数が機能する方法にすぎません。そうしないと、次のエラーが発生しますcannot convert from 'double*' to 'double **'
。テストサンプルが1次元になれない理由を説明したと思います。
- この
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'警告は完全に有効ですが、問題は他の警告とその注意です。コンパイラから同様のものを取得する必要があります。
その考え方や機能は理解できたと思いますが、それでもコードには理解できないことがたくさんあります。先生にプレゼンテーションをしているので、すべてのセリフを表現できるはずです。
あなたが何も表示していないために他の誰かがあなたにコードを提供すると、あなたは彼らが何をしているのか理解できないリスクを冒します。
コードを教師に提示するにはコードを理解する必要があるため、プログラミングの演習を行う必要があります。私が最初にしたことの1つは、問題をおもちゃのサイズに縮小することでした(2065ではなく、5、10、または20を使用しました)。あなたは同じことをするべきです。id
固定サイズの要素( 、、、およびt3
)のみを含む構造から始めます。初期化してエクスポートおよびインポートできるようにします。エクスポートした変数とは異なる変数にインポートしてから、2つの変数の比較を行うことができます。最初のバージョンでは省略できます。kernel_par
test_sample
test_sample
それが機能するようになったら、配列の1つとそのディメンションメンバーを追加します。今それを動作させます(サイズ4x5または同様のもので)。次に、他の配列を追加します(簡単なはずです)。これを行うと、私が示した例のさまざまな関数が何をするのか、そしてなぜそれらがそこにあるのかがわかるはずです。それらはすべて、あるレベルで「必要」です。コメントでほのめかしたように、それを正しくするために数回(多すぎる)の試みが必要でした。私は厳密な警告オプションを使用してコンパイルしていましたが、まだvalgrind
初期化されていないデータについて悩んでいました(投稿しようとしていたため)。しかし、私は最終的に、不完全に編集されたコピーアンドペーストのコードを見つけました。
データのエクスポートを試みるという正しい仕事、できればデータのインポートを試みるという正しい仕事をしたコードを投稿した場合、そのコードは修正されている可能性があることに注意してください。価値のあるコードを投稿しなかったため、テスト可能なものを作成せずに、実際の問題に対処するコードを作成することは困難でした。私が提供したコードはテスト可能です。テストはより包括的である可能性があります—はい、間違いなく。しかし、コードをテスト可能にし、それをテストすることは、プログラミングを学ぶ上で重要な部分です。
ちなみに、任意のタイプ(配列など)の可変長データのエクスポートプロセスの重要なポイントは、データ(配列)自体が書き込まれる前に、データ(配列)のサイズが書き込まれることを確認することです。次に、インポートプロセスは、データ(配列)を読み戻す前に、割り当てるスペースの量を認識します。