概要
- C言語で型安全なジェネリックデータ構造を実現する独自手法の紹介
- unionを活用し、型情報をデータ構造に関連付ける方法
- 既存のマクロ多重インクルード法やvoid *による手法との比較
- 柔軟配列メンバやtypeofによる型安全性の向上
- typedefによる型名解決や複数型対応の拡張性
C言語における型安全なジェネリックデータ構造の実装手法
- union を用いて、ジェネリックなデータ構造に 型情報 を埋め込む独自手法
- List, Map, Tree など様々なデータ構造に適用可能
- 本記事では 基本的なリンクリスト を例に実装方法を解説
従来手法:マクロ多重インクルードによる型安全性
- データ構造を ヘッダファイル でマクロ展開し、型ごとに複数回インクルード
- 例:
#define T Foo#include "list.h"- 型ごとに関数名や構造体名が生成 される(例: Foo_list_prepend)
- 欠点
- 定義箇所の特定が困難 (マクロ展開による可読性低下)
- コード補完やリファクタリングが難しい
- バイナリサイズやビルド時間の増大
- 型名プレフィックス付き関数 が必要(例: Foo_list_prepend)
Genericsレベル1:void *による汎用化
- *void 型 でデータ構造を汎用化
- 例:
struct ListNode { ListNode *next; void *data; };list_prepend(ListNode **head, void *data);
- 欠点
- 型安全性が失われる
- 2回のメモリアクセス が必要(ノードとデータが別アロケーション)
Genericsレベル2:インラインストレージ(柔軟配列メンバ)
- 柔軟配列メンバ を利用し、ノードとデータを 同一メモリブロック に格納
- 例:
struct ListNode { ListNode *next; uint64_t data[]; };list_prepend(ListNode **head, void *data, size_t data_size);
- パフォーマンス向上 と メモリ効率化
- nextとdataが隣接することで キャッシュ効率 向上
- 欠点
- data_sizeの明示的指定 が必要
Genericsレベル3:typeofとunionによる型安全性
- union を用いてリストに型情報を持たせる
- 例:
#define List(type) union { ListNode *head; type *payload; }
- 例:
- payload はランタイムで未使用、 型情報のみを保持
- typeof を使い、 list_prependマクロ で型安全な関数呼び出しを実現
#define list_prepend(list, item) ((void (*)(ListNode **, __typeof__((list)->payload), size_t))_list_prepend) (&((list)->head), item, sizeof(*(list)->payload))
- 型不一致時はコンパイルエラー
- 例:int型リストにFoo型を追加するとエラー
typeof未対応コンパイラへの対応
- typeof非対応の場合 はstructで型情報を保持
- 例:
#define List(type) struct { ListNode *head; type *payload; }
- 例:
- payloadへの代入で型チェック を実現
#define list_prepend(list, item) do { (list)->payload = (item); _list_prepend(&((list)->head), item, sizeof(*(list)->payload)); } while(0)
typedefによる型名の統一
- typedef でList(Foo)型をListFooなどに定義
- 関数引数や代入時の型不一致エラー を回避
- 例:
typedef List(Foo) ListFoo;ListFoo a; ListFoo b = a; void my_function(ListFoo list); my_function(a);
応用:複数型対応データ構造
- Map など複数型を持つデータ構造も同様に拡張可能
- 例:
#define Map(key_type, value_type) union { MapInternal map; key_type *key; value_type *value; }
- 例:
まとめ・参考
- union + typeof による型安全なジェネリックデータ構造
- 柔軟な拡張性 と 型安全性 を両立
- stb_ds.h など他の型安全実装との比較
- typeof はC23で標準化、主要コンパイラで広くサポート
参考リンク・謝辞
- stb_ds.h :配列やマップの型安全な実装例
- Martin Fouilleul :記事執筆の励ましと初期フィードバック