動作原理

内部情報ヘッダファイル

内部情報ヘッダファイルには、以下の情報が記述されています。

  1. 型情報
    それぞれのデータ型の情報をintの配列で表現したものです。
    具体的には、型名・サイズ・構造体の場合、メンバのオフセットなどです。 これらの情報のうち、環境に依存するものは、 アプリケーションのコンパイル時にCコンパイラに計算させています。 よって、内部情報ヘッダファイル自体は、環境非依存です。
  2. 初期化子定義
    それぞれのデータ型ごとに、初期化の際のテンプレートとなる実体を staticに定義し、Cコンパイラに初期化させています。
    SERが、共用体に対して満足にデフォルト値を与えることができなかったり、 不完全型にスカラしか許さなかったりするのは、この辺に由来します。
  3. 文字列テーブル
    SERは、デシリアライズ時にデータ型・列挙子・構造体メンバ等を 特定するために、その名前(文字列)をキーとします。文字列テーブルには、 そのための文字列がchar *の配列として保持されています。
  4. スキーマ定義
    上記データをとりまとめ、ひとつのグローバル変数に定義します。 SER_create_storage()の時に渡すスキーマは、このグローバル変数へのポインタです。

内部情報ヘッダファイルの例


メモリ確保

SERでは、malloc()によりシステムからまとまった領域を確保し、 SER_malloc()がコールされる度に、その領域を切り売りしています。

malloc()により確保したひとかたまりの領域は、「ページ」と呼び、 連結リストにより管理しています。

空き領域は、サイズ毎に分類し、外部連鎖ハッシュで管理することにより、 メモリ確保の効率化を図っています(全然チューンしてないので、 効果の程は定かではないですが)。


ガベージコレクション

自動free

SERでは、mark-sweep方式のガベージコレクトを行なっています。 SERでは、SERを使用して確保されたデータの型を保持しており、 かつ、グローバル・static・ローカル変数から指されているオブジェクトを 完璧に無視しているため、保守的でない回収が可能になるわけです。

使用中のオブジェクトのマークは、単純にスタックを使用して 再帰呼び出しにより行なっています。

このアルゴリズムでは、大量のスタックを消費するので、 改良する必要があると思います。

...が、再帰呼び出しで、ひたすら長い連結リストをマークしても、 今時の環境では結構動いてしまうものなのですね。(^^;

走査法でマークする方法もやってみたのですが、 やっぱりオーダがデータ数の2乗になってしまうようなアルゴリズムは、 実用にはならないようです。

コンパクション

SERでは、コンパクションの際、以下の処理を行ないます。

  1. 各オブジェクトに、順に整数型の「オブジェクトID」を付ける。
  2. 各オブジェクトのポインタを、オブジェクトIDに置換する。
  3. オブジェクトを移動する。
  4. オブジェクトIDをポインタに復元する。
    この際、充分なメモリがあれば、 オブジェクトの数だけのポインタテーブルを作成しますが、 メモリが不足した場合は、毎回ポインタを求めるために オブジェクトを頭から検索します。 この場合、所要時間がオブジェクトの数の2乗に比例するわけで、 メモリが足りなくなるほど大量のオブジェクトを扱っている状況では、 ほとんど実用にならないと思いますが... (だいたいこっちの動きの時のテストはしてないのでした(^^;;)

エンコーダとデコーダ

シリアライズの際、SERは、データをCにおける基本的な データ型(char, short, int, float, double およびそれぞれの整数型のunsigned) まで解析した後、「エンコーダ」をコールします。

エンコーダは、SERから受け取ったデータを何らかの形でコード化し、 指定されたストリームに出力します。

デコーダは、エンコーダの逆の働きをします。

エンコーダ・デコーダは、

  エンコード方式 × Cの基本型の数

だけ存在します。

現時点で、エンコード方式は"ascii_text"しかありませんが、 その他、例えば"char8short16int32long32bigendian"等が考えられます。

アプリケーションプログラマが、独自にエンコーダ・デコーダを記述し、 エンコード方式を追加することも可能です(SER_encode.hを参照のこと)。


シリアライズ

シリアライズでは、SERは、以下の処理を行ないます。

  1. エンコーダを使用してヘッダ情報を出力する。 ヘッダ情報には、スキーマ情報が含まれている。
  2. (コンパクションの時と同様)ポインタをオブジェクトIDに置換する。
  3. 全てのオブジェクトについて、以下の処理を行なう。
    1. SER_malloc()の際に与えられた型情報を、エンコーダを使用して出力する。
    2. SER_malloc()の際に与えられた型情報を元に オブジェクトのデータ型を解析し、 Cにおける基本型まで分解したら、エンコーダをコールする。

デシリアライズ

デシリアライズでは、以下の処理を行ないます。

  1. ストリームから、ヘッダ情報を読み込む。
  2. ヘッダ情報には、元データのスキーマ情報が含まれているので、 デシリアライズ先のスキーマ情報と比較し、「型コンバータ」を 作成する。
  3. ストリームから以下の手順でオブジェクトを読み込む。
    1. デコーダを使用して型情報を読み込む。
    2. 読み込んだ型情報と元データのスキーマ情報から、 デコーダをコールしデータを読み込む。
    3. 型コンバータを使用して、適切なオフセットに、 必要に応じて変換したデータを格納する。
  4. ポインタの格納されるべきアドレスには、オブジェクトIDが 保存されているので、それをポインタに置換する。

本来なら、シリアライズの時にも型コンバータを作成して、 任意のスキーマで出力できるようにしておくべきだったのでしょうが... 忘れてました(^^;


型コンバータ

現時点で、型コンバータは、以下のように動作します。

  1. デシリアライズ先の構造体・共用体に、メンバが増えていた場合、 そのメンバにはデフォルトの値を埋め、残りのメンバについて、 メンバ名をキーに新しいオフセットに復元する。
  2. デシリアライズ先の列挙に列挙子が増えていた場合、 列挙子名をキーに正しい値に変換して格納する。 これにより、列挙のど真ん中に列挙子を追加しても、 正しくデータを読み込むことができる。
  3. デシリアライズ先の構造体・共用体・列挙で メンバが減っていた場合、 デシリアライズの開始時点で警告を発する。 その後、その減ったメンバを使用したデータを実際に読み込んだ 時点で、デシリアライズを中断しエラーリターンする。

この方針は、データを拡張した方向には割と甘いのですが、 逆方向の型変換については厳し過ぎるような気もします。

しかし、データが欠落するような変換をライブラリが 勝手に行なうことには抵抗があったので、 取り敢えず厳しい方に振っておきました。


ひとつ上のページに戻る | prev(使い方) | next(スキーマファイルの文法)