概要
- 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導入やパフォーマンス低下などのトレードオフ
- 既存資産の安全化 やバグ検出の補助、型安全な関数ポインタ運用などに有用