Fil-Cの簡易モデル
概要
- Fil-CはC/C++のメモリ安全性を実現するための実装
- ソースコードを書き換え、各ポインタにAllocationRecord*を付随
- メモリ管理に**ガーベジコレクタ(GC)**を導入
- 標準関数もFil-Cバージョンに自動変換
- パフォーマンス低下と引き換えに、安全性向上
Fil-C: C/C++のメモリ安全実装の仕組み
- Fil-CはC/C++コードを自動で書き換え、メモリ安全性を確保
- 本来はLLVM IRを書き換えるが、簡易版ではC/C++ソースコードを書き換え
- 各関数内のポインタ型ローカル変数ごとにAllocationRecord*変数を追加
- 例:
T1* p1;→T1* p1; AllocationRecord* p1ar = NULL;
- 例:
- AllocationRecord構造体は、可視バイト列・不可視バイト列・長さを管理
- ポインタ間の代入や演算時、AllocationRecord*も一緒に移動
- ポインタを引数や戻り値として渡す際も、AllocationRecord*をセットで扱う
- 標準ライブラリ関数(例: malloc, free)もFil-Cバージョンに置き換え
- 例:
{p1, p1ar} = filc_malloc(x); ... filc_free(p1, p1ar);
- 例:
filc_mallocの仕組み
- filc_mallocは3つのメモリアロケーションを行う
- AllocationRecord本体
- visible_bytes(実際のデータ領域)
- invisible_bytes(ポインタのメタデータ領域)
- invisible_bytesはAllocationRecord*型の配列として機能
ポインタのデリファレンスとバウンドチェック
- ポインタ参照時、AllocationRecord*で境界チェックを実施
- NULLチェック、範囲チェック、型サイズチェック
- ポインタ値自体のロード/ストア時には、invisible_bytesで対応するAllocationRecord*もロード/ストア
filc_freeの動作
- filc_freeはvisible_bytesとinvisible_bytesのみ解放
- AllocationRecord本体はGCによって後で解放
ガーベジコレクタ(GC)の役割
- 到達不能なAllocationRecordを探索し、filc_freeを呼び出して解放
- 長さ0のAllocationRecordへのポインタは、共通の“空”AllocationRecordに書き換え
- free忘れによるメモリリークをGCが補償
- ローカル変数のアドレスがスコープ外で使われる場合、ヒープに昇格しGC管理
標準関数の特殊対応(memmove例)
- memmoveなど任意メモリ操作関数は、invisible_bytesも正しくコピー
- バイト単位のmemmoveと、アライン済みポインタ単位のmemmoveで挙動が異なる
Fil-Cの追加的な複雑性
- スレッド対応:GCや解放のタイミングが複雑化
- アトミック操作:ポインタとAllocationRecord*の同時操作が必要
- 関数ポインタ:AllocationRecordに関数ポインタ情報を保持し、型安全性を確保
- メモリ最適化:invisible_bytesの遅延確保やAllocationRecordとvisible_bytesの統合
- パフォーマンス最適化:安全性の代償としての性能低下への対策
Fil-Cの利用シーン
-
既存C/C++コードのメモリ安全性確保
- GC導入と性能低下を許容できる場合の一時的措置
-
ASanのようなバグ検出目的での利用
-
コンパイル時評価の安全性確保(例: Zig)
-
ポインタプロヴナンス(ポインタの来歴管理)の具体例
- 例:
if (p1 == p2) { f(p1); }とif (p1 == p2) { f(p2); }の違いが明確化 - AllocationRecord*の伝播による違いが生じるため、単純な書き換えが無効
- 例:
まとめ
- Fil-CはC/C++に本格的なメモリ安全性をもたらす
- GC導入やパフォーマンス低下などのトレードオフ
- 既存資産の安全化やバグ検出の補助、型安全な関数ポインタ運用などに有用