第9章:ソフトウェアエンジニアリングの原理 - ライブラリ設計と実世界への応用
9.1 ソフトウェアモジュール性の理論
9.1.1 モジュール性の歴史と哲学
プログラミングの危機(Software Crisis)
1960年代後半、コンピュータの性能向上により、より大規模なソフトウェアが求められるようになった。しかし、従来のプログラミング手法では、大規模システムの開発・保守が困難になる「ソフトウェアの危機」が顕在化した。
1968年のNATO Software Engineering Conferenceで、この問題が正式に提起され、「ソフトウェアエンジニアリング」という分野が誕生した。
David Parnasの情報隠蔽原理
1972年、David Parnasは画期的な論文「On the Criteria To Be Used in Decomposing Systems into Modules」を発表した。この論文で、彼は情報隠蔽(Information Hiding)の原理を提唱した:
Parnas's Principle:
モジュールは変更されやすい設計決定を隠蔽すべきである。
モジュール間のインターフェースは、各モジュールの内部実装について
可能な限り少ない情報を露出すべきである。
Parnasは、従来のフローチャートベースの分解ではなく、「変更の可能性」に基づくモジュール分解を提唱した:
【従来のアプローチ:フローチャート分解】
main() → input() → process() → output()
各モジュールは処理の流れに従って分割
問題:一つの変更が多くのモジュールに影響
【Parnasのアプローチ:情報隠蔽分解】
┌─────────────────────────────────────────┐
│ Application │
├─────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ UI │ │ Business│ │ Data │ │
│ │ Module │ │ Logic │ │ Access │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Stable Interfaces Only │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
各モジュールは変更されやすい決定を隠蔽
インターフェースは安定した抽象のみを公開
モジュール性の評価尺度
1. 結合度(Coupling)
モジュール間の依存度を測る尺度。低いほど良い。
結合度の階層(弱い → 強い):
1. データ結合(Data Coupling)
- モジュール間でプリミティブデータのみを受け渡し
- 最も望ましい形態
例:function(int x, int y)
2. スタンプ結合(Stamp Coupling)
- モジュール間で複合データ構造を受け渡し
- 使用しないフィールドも含まれる
例:function(struct entire_record rec)
3. 制御結合(Control Coupling)
- モジュールが別モジュールの処理を制御するフラグを渡す
例:function(data, bool special_mode_flag)
4. 共通結合(Common Coupling)
- グローバル変数を共有
例:複数のモジュールがglobal_configを参照
5. 内容結合(Content Coupling)
- モジュールが別モジュールの内部データを直接参照
- 最も避けるべき形態
例:直接他モジュールの内部変数を変更
2. 凝集度(Cohesion)
モジュール内の要素がどれだけ関連しているかを測る尺度。高いほど良い。
凝集度の階層(弱い → 強い):
1. 偶発的凝集(Coincidental)
- 要素間に意味のある関連がない
- 最悪の形態
例:misc.c(雑多な関数の寄せ集め)
2. 論理的凝集(Logical)
- 論理的に類似した機能をまとめる
例:input.c(すべての入力処理)
3. 時間的凝集(Temporal)
- 同時に実行される処理をまとめる
例:initialization.c(すべての初期化)
4. 手続き的凝集(Procedural)
- 特定の順序で実行される処理をまとめる
例:process_transaction.c
5. 通信的凝集(Communicational)
- 同じデータを操作する処理をまとめる
例:customer_data.c(顧客データの全操作)
6. 逐次的凝集(Sequential)
- 出力が次の入力となる処理の連鎖
例:pipeline_stage.c
7. 機能的凝集(Functional)
- 単一の明確な機能を実行
- 最も望ましい形態
例:calculate_checksum.c
9.1.2 ライブラリ設計の原理
APIデザインの原則
1. 最小驚きの原則(Principle of Least Surprise)
// 驚きの少ないAPI設計
// Good: 標準的な命名と動作
size_t ft_strlen(const char *s); // 文字列の長さを返す
// Bad: 予期しない副作用
size_t strlen_and_print(const char *s); // 長さを返すが、なぜか表示もする
// Good: 一貫した戻り値規約
// 成功: 0, 失敗: -1 (errno設定)
int ft_function(args);
// Bad: 不一致な戻り値
// ある関数は成功で0、別の関数は成功で1
2. 直交性の原則(Orthogonality)
機能が独立しており、自由に組み合わせ可能であること:
// 直交的なAPI設計
// 各関数は単一の責務
void *ft_memcpy(void *dst, const void *src, size_t n);
void *ft_memset(void *b, int c, size_t len);
void *ft_memmove(void *dst, const void *src, size_t n);
// 組み合わせて使用可能
void example(void)
{
char buf[100];
ft_memset(buf, 0, sizeof(buf)); // 初期化
ft_memcpy(buf, "Hello", 5); // コピー
ft_memmove(buf + 2, buf, 3); // オーバーラップ移動
}
// 非直交的な設計(避けるべき)
// ft_memcpy_and_convert() - コピーと変換を同時に行う
// ft_memset_if_empty() - 条件付きで設定
3. ゼロオーバーヘッドの原則(Zero-overhead Principle)
Bjarne Stroustrupが提唱したC++の設計原則:
「使わない機能にはコストをかけない」
「使う機能については、手書きより効率が悪くならない」
Libftへの適用:
// ゼロオーバーヘッドの例
// デバッグ機能はコンパイル時に除去可能
#ifdef DEBUG
#define ASSERT(cond) do { if (!(cond)) abort(); } while(0)
#else
#define ASSERT(cond) ((void)0)
#endif
// インライン化によるオーバーヘッド除去
static inline int ft_isspace(int c)
{
return (c == ' ' || c == '\t' || c == '\n' ||
c == '\v' || c == '\f' || c == '\r');
}
// 使わない機能(エラーチェック)を選択可能
#ifdef LIBFT_SAFE
#define CHECK_NULL(ptr) if (!(ptr)) return NULL
#else
#define CHECK_NULL(ptr) ((void)0)
#endif
9.1.3 契約による設計(Design by Contract)
Bertrand Meyerが1986年に提唱した設計手法。各関数は呼び出し側と実装側の間の「契約」として定義される。
契約の三要素
/*
* ft_substr - 部分文字列を抽出する
*
* 事前条件(Preconditions)- 呼び出し側の責任:
* - s != NULL
* - start < ft_strlen(s) の場合、有効な部分文字列を抽出
* - len >= 0
*
* 事後条件(Postconditions)- 実装側の責任:
* - 戻り値 != NULL(メモリ確保成功の場合)
* - 戻り値は新しく確保されたメモリ
* - 戻り値はnull終端された文字列
* - len(戻り値) == min(len, strlen(s) - start)
*
* 不変条件(Invariants):
* - sは変更されない
*/
char *ft_substr(char const *s, unsigned int start, size_t len)
{
// 事前条件のチェック(デバッグビルド時)
REQUIRE(s != NULL);
// 実装
...
// 事後条件のチェック(デバッグビルド時)
ENSURE(result != NULL || allocation_failed);
ENSURE(result == NULL || ft_strlen(result) <= len);
return result;
}
DbCマクロの実装
// contract.h - 契約による設計のサポート
#ifndef CONTRACT_H
# define CONTRACT_H
# include <stdio.h>
# include <stdlib.h>
# ifdef DEBUG
// 契約違反のハンドラ
static inline void contract_violation(const char *type, const char *cond,
const char *file, int line)
{
fprintf(stderr, "Contract violation [%s]: %s\n", type, cond);
fprintf(stderr, " at %s:%d\n", file, line);
abort();
}
// 事前条件
# define REQUIRE(cond) \
do { if (!(cond)) contract_violation("REQUIRE", #cond, __FILE__, __LINE__); } while(0)
// 事後条件
# define ENSURE(cond) \
do { if (!(cond)) contract_violation("ENSURE", #cond, __FILE__, __LINE__); } while(0)
// 不変条件
# define INVARIANT(cond) \
do { if (!(cond)) contract_violation("INVARIANT", #cond, __FILE__, __LINE__); } while(0)
# else
// リリースビルドでは何もしない
# define REQUIRE(cond) ((void)0)
# define ENSURE(cond) ((void)0)
# define INVARIANT(cond) ((void)0)
# endif
#endif
---
9.2 ビルドシステムの理論
9.2.1 コンパイルモデルの歴史
分割コンパイルの発明
1950年代、初期のコンピュータプログラムは単一のソースファイルから直接機械語に変換されていた。プログラムが大規模化するにつれ、以下の問題が発生:
- コンパイル時間の爆発:小さな変更でも全体の再コンパイルが必要
- メモリ制限:コンパイラが巨大なソースを処理できない
- チーム開発の困難:複数人が同時に同じファイルを編集できない
この問題を解決するため、「分割コンパイル(Separate Compilation)」が発明された。
翻訳単位(Translation Unit)の概念
【分割コンパイルモデル】
┌─────────────────────────────────────────────────────────────────┐
│ Source Files │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ main.c │ │ string.c │ │ memory.c │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Preprocessor │ │
│ │ #include, #define, #ifdef の処理 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Translation│ │Translation│ │Translation│ │
│ │ Unit 1 │ │ Unit 2 │ │ Unit 3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Compiler │ │
│ │ 字句解析 → 構文解析 → 意味解析 → 最適化 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ main.o │ │ string.o │ │ memory.o │ │
│ │(relocatable) │(relocatable) │(relocatable) │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Linker │ │
│ │ シンボル解決 │ │
│ │ アドレス再配置 │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Executable │ │
│ │ または │ │
│ │ Library │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
リンクの段階
【シンボル解決のプロセス】
string.c:
size_t ft_strlen(const char *s) // 定義
{
...
}
main.c:
#include "libft.h"
int main(void)
{
size_t len = ft_strlen("hello"); // 参照
...
}
コンパイル後:
string.o:
Symbol Table:
| Name | Type | Section | Value |
|-----------|----------|---------|----------|
| ft_strlen | GLOBAL | .text | 0x0000 | ← 定義
main.o:
Symbol Table:
| Name | Type | Section | Value |
|-----------|----------|---------|----------|
| main | GLOBAL | .text | 0x0000 |
| ft_strlen | UNDEF | - | - | ← 未解決参照
リンク時:
1. 未解決参照(ft_strlen)を発見
2. 他のオブジェクトから定義を検索
3. string.oでft_strlenの定義を発見
4. 参照を正しいアドレスに解決
9.2.2 依存関係グラフとインクリメンタルビルド
依存関係の有向非巡回グラフ(DAG)
ビルドシステムの核心は、ソースファイル間の依存関係を有向非巡回グラフ(Directed Acyclic Graph)として管理することにある。
【Libftの依存関係グラフ】
┌───────────────┐
│ libft.a │
└───────┬───────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌───────┐ ┌────────┐ ┌────────┐
│ ft_*.o│ │ ft_*.o │ │ ft_*.o │
│(ctype)│ │(string)│ │(memory)│
└───┬───┘ └───┬────┘ └───┬────┘
│ │ │
│ │ │
▼ ▼ ▼
┌───────┐ ┌────────┐ ┌────────┐
│ ft_*.c│ │ ft_*.c │ │ ft_*.c │
│(ctype)│ │(string)│ │(memory)│
└───┬───┘ └────┬───┘ └────┬───┘
│ │ │
└───────────────┼───────────────┘
│
▼
┌───────────────┐
│ libft.h │
└───────────────┘
ルール:
- エッジの方向は依存の方向(A→Bは「AはBに依存」)
- ヘッダファイル変更 → 依存する全.oを再コンパイル
- .cファイル変更 → 対応する.oのみ再コンパイル
- .o変更 → ライブラリを再構築
インクリメンタルビルドの理論
【再ビルド判定アルゴリズム】
function needs_rebuild(target):
if target does not exist:
return true
for each dependency of target:
if needs_rebuild(dependency):
return true
if modification_time(dependency) > modification_time(target):
return true
return false
【例:libft.aの再ビルド判定】
libft.a の依存: ft_strlen.o, ft_memcpy.o, ft_strcpy.o, ...
Case 1: ft_strlen.c を編集
- ft_strlen.c の mtime > ft_strlen.o の mtime
- ft_strlen.o を再コンパイル
- ft_strlen.o の mtime > libft.a の mtime
- libft.a を再構築
Case 2: libft.h を編集
- libft.h の mtime > 全.o の mtime
- 全.o を再コンパイル
- libft.a を再構築
Case 3: 変更なし
- すべての依存が最新
- 何もしない
Makefileの理論的基礎
# Makefile は本質的に依存関係グラフの宣言と
# 再構築ルールの定義である
# フォーマット:
# target: dependencies
# recipe (タブ文字で始まる)
# ターゲット: ライブラリ
# 依存: すべてのオブジェクトファイル
# レシピ: アーカイブコマンド
$(NAME): $(OBJS)
$(AR) $(ARFLAGS) $@ $^
# ターゲット: オブジェクトファイル (.o)
# 依存: ソースファイル (.c) とヘッダ
# レシピ: コンパイルコマンド
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c ___CODE_BLOCK_14___lt; -o $@
# パターンルール解説:
# % - ステムマッチ(任意の文字列)
# ___CODE_BLOCK_14___lt; - 最初の依存ファイル
# $@ - ターゲット名
# $^ - すべての依存ファイル
9.2.3 静的ライブラリと動的ライブラリ
ライブラリの種類と特徴
【静的ライブラリ(.a / .lib)】
コンパイル時:
┌─────────────┐ ┌─────────────┐
│ main.o │ │ libft.a │
│ │ │ ┌─────────┐ │
│ call strlen ├────►│ │ft_strlen│ │
│ │ │ └─────────┘ │
│ │ │ ┌─────────┐ │
│ call memcpy ├────►│ │ft_memcpy│ │
│ │ │ └─────────┘ │
└─────────────┘ └─────────────┘
│ │
└────────┬─────────┘
│
▼
┌─────────────────┐
│ 実行ファイル │
│ ┌─────────────┐ │
│ │ main code │ │
│ ├─────────────┤ │
│ │ ft_strlen │ │ ← ライブラリコードが
│ │ (コピー) │ │ 埋め込まれる
│ ├─────────────┤ │
│ │ ft_memcpy │ │
│ │ (コピー) │ │
│ └─────────────┘ │
└─────────────────┘
利点:
- 実行時に外部依存なし
- 配布が容易
- 実行時オーバーヘッドなし
欠点:
- 実行ファイルサイズが大きい
- ライブラリ更新時は再リンクが必要
- 複数プログラムでメモリ共有不可
【動的ライブラリ(.so / .dll)】
コンパイル時:
┌─────────────┐
│ main.o │
│ │
│ call strlen ├──────► "ft_strlen" (シンボル参照のみ)
│ │
│ call memcpy ├──────► "ft_memcpy" (シンボル参照のみ)
└─────────────┘
│
▼
┌─────────────────┐
│ 実行ファイル │
│ ┌─────────────┐ │
│ │ main code │ │
│ ├─────────────┤ │
│ │ 動的リンク │ │ ← 実行時に解決
│ │ テーブル │ │
│ └─────────────┘ │
└─────────────────┘
実行時:
┌─────────────────┐ ┌─────────────────┐
│ 実行ファイル │ │ libft.so │
│ │ │ (共有メモリ) │
│ call strlen ────┼────►│ ft_strlen │
│ │ │ │
│ call memcpy ────┼────►│ ft_memcpy │
└─────────────────┘ └─────────────────┘
利点:
- 実行ファイルサイズが小さい
- ライブラリ更新で再リンク不要
- 複数プログラムでコード共有
欠点:
- 実行時に動的リンカが必要
- わずかな実行時オーバーヘッド
- 依存関係管理が複雑
位置独立コード(PIC: Position Independent Code)
動的ライブラリは任意のアドレスにロードされる可能性があるため、位置独立コードが必要。
【絶対アドレス vs 相対アドレス】
// 絶対アドレス(静的ライブラリ向け)
mov eax, [0x08040000] // グローバル変数のアドレスが固定
// 位置独立コード(動的ライブラリ向け)
// GOT (Global Offset Table) を使用
call get_pc // 現在のPC値を取得
add eax, _GLOBAL_OFFSET_TABLE_
mov eax, [eax + var@GOT] // GOT経由でアドレスを解決
【GOTとPLTの仕組み】
┌──────────────────┐
│ Code Section │
│ │
│ call printf@PLT ├───┐
│ │ │
└──────────────────┘ │
│
┌──────────────────┐ │
│ PLT (Procedure │◄──┘
│ Linkage Table) │
│ │
│ printf@PLT: │
│ jmp *printf@GOT├───┐
│ push index │ │
│ jmp resolver │ │
└──────────────────┘ │
│
┌──────────────────┐ │
│ GOT (Global │◄──┘
│ Offset Table) │
│ │
│ printf: 0x... │ ← 最初は遅延解決スタブ
│ (実アドレス) │ 呼び出し後に実アドレス
└──────────────────┘
---
9.3 メモリアロケータの理論
9.3.1 動的メモリ管理の歴史
メモリ管理の進化
【歴史的発展】
1950年代: 静的メモリ割り当てのみ
プログラム開始時にすべてのメモリを確保
1960年代: スタックベースの自動管理
関数呼び出しに伴う自動確保・解放
LIFO(後入れ先出し)制約
1960年代後半: ヒープによる動的割り当て
malloc/freeの登場
任意の順序での確保・解放
1970年代: ガベージコレクション
LISP、Smalltalkで自動メモリ管理
参照カウント、マーク&スイープ
1980年代: 領域ベースメモリ管理(Region-based)
階層的なメモリ領域の概念
アリーナアロケータの登場
2000年代: スラブアロケータ(Slab Allocator)
カーネル向け特化型アロケータ
jemalloc, tcmalloc等の高性能アロケータ
9.3.2 アロケータの設計原理
フラグメンテーション問題
【外部フラグメンテーション】
初期状態:
┌─────────────────────────────────────────┐
│ Free │
└─────────────────────────────────────────┘
A, B, C を順に確保:
┌─────┬─────┬─────┬───────────────────────┐
│ A │ B │ C │ Free │
└─────┴─────┴─────┴───────────────────────┘
B を解放:
┌─────┬─────┬─────┬───────────────────────┐
│ A │Free │ C │ Free │
└─────┴─────┴─────┴───────────────────────┘
問題: 総フリー容量は十分だが、
連続した大きな領域がない
【内部フラグメンテーション】
要求: 100バイト
確保: 128バイト(アラインメント要件)
無駄: 28バイト(使用されない)
┌───────────────────────────┐
│ 100 bytes used │ 28 waste │
└───────────────────────────┘
アロケータアルゴリズム
1. First-Fit(最初適合)
リストを先頭から走査し、最初に見つかった
十分なサイズのブロックを使用
Free list: [64] → [128] → [256] → [512]
要求: 100バイト
結果: [128]ブロックを使用
利点: シンプル、高速
欠点: 先頭付近に小さな断片が蓄積
2. Best-Fit(最適適合)
リスト全体を走査し、要求に最も近い
サイズのブロックを使用
Free list: [64] → [128] → [256] → [512]
要求: 100バイト
結果: [128]ブロックを使用(差が最小)
利点: 無駄が少ない
欠点: 検索が遅い、極小断片が発生
3. Segregated Free Lists
サイズクラスごとに別々のフリーリストを管理
mermaid
graph LR
L16[16 bytes] --> B16_1[■] --> B16_2[■] --> NULL
L32[32 bytes] --> B32_1[■] --> NULL
L64[64 bytes] --> B64_1[■] --> NULL
L128[128 bytes] --> B128_1[■] --> B128_2[■] --> NULL
L256[256 bytes] --> NULL
L512[512+ bytes] --> B512_1[■] --> NULL
要求: 50バイト → 64バイトクラスから即座に取得
O(1)での割り当てが可能
9.3.3 メモリプールとアリーナ
メモリプールの理論
同一サイズのオブジェクトを大量に確保・解放する場合、専用のメモリプールが効率的。
// メモリプールの理論的構造
/*
* 固定サイズブロックプール
*
* 設計目標:
* - O(1) 割り当て
* - O(1) 解放
* - 外部フラグメンテーションなし
* - 内部フラグメンテーション最小化
*/
typedef struct s_memory_pool {
void *memory; // プリアロケートされたメモリ領域
size_t block_size; // 各ブロックのサイズ(固定)
size_t block_count; // 総ブロック数
void **free_list; // フリーブロックのスタック
size_t free_count; // 現在のフリーブロック数
} t_memory_pool;
/*
* 確保操作: O(1)
*
* 1. free_listのトップからポインタを取得
* 2. free_countをデクリメント
* 3. ポインタを返す
*/
void *pool_alloc(t_memory_pool *pool)
{
if (pool->free_count == 0)
return NULL; // プール枯渇
return pool->free_list[--pool->free_count];
}
/*
* 解放操作: O(1)
*
* 1. ポインタをfree_listのトップに追加
* 2. free_countをインクリメント
*/
void pool_free(t_memory_pool *pool, void *ptr)
{
// 境界チェック(オプション)
if (ptr < pool->memory ||
ptr >= (char*)pool->memory + pool->block_size * pool->block_count)
return;
pool->free_list[pool->free_count++] = ptr;
}
アリーナアロケータ
一括解放パターンに特化したアロケータ。個別解放をサポートせず、領域全体を一度に解放する。
// アリーナアロケータの理論
/*
* アリーナ(領域)ベースのメモリ管理
*
* 使用パターン:
* 1. アリーナを作成
* 2. アリーナから複数のオブジェクトを確保
* 3. 作業完了後、アリーナ全体を一括解放
*
* 典型的な用途:
* - リクエスト処理(リクエスト終了時に全解放)
* - パーサー(パース完了時に全解放)
* - ゲームフレーム(フレーム終了時に全解放)
*/
typedef struct s_arena {
void *memory; // 現在のメモリチャンク
size_t size; // チャンクサイズ
size_t used; // 使用量
struct s_arena *next; // 次のチャンク(チェーン)
} t_arena;
typedef struct s_arena_allocator {
t_arena *first; // 最初のチャンク
t_arena *current; // 現在のチャンク
size_t default_size; // デフォルトチャンクサイズ
} t_arena_allocator;
/*
* 確保: O(1) 平均(チャンク拡張を除く)
*
* 単純なポインタ加算のみ
* 個別のメタデータ管理不要
*/
void *arena_alloc(t_arena_allocator *arena, size_t size)
{
// アラインメント調整
size = (size + 7) & ~7;
// 現在のチャンクに空きがあるか
if (!arena->current || arena->current->used + size > arena->current->size) {
// 新しいチャンクが必要
t_arena *new_chunk = create_chunk(
size > arena->default_size ? size : arena->default_size
);
if (!new_chunk)
return NULL;
// チェーンに追加
if (arena->current)
arena->current->next = new_chunk;
else
arena->first = new_chunk;
arena->current = new_chunk;
}
// バンプポインタ方式で確保
void *ptr = (char*)arena->current->memory + arena->current->used;
arena->current->used += size;
return ptr;
}
/*
* 個別解放: サポートしない
*
* これは制限ではなく、設計上の選択
* 個別解放を不要とすることで:
* - メタデータオーバーヘッドなし
* - フラグメンテーションなし
* - 高速な確保
*/
void arena_free(t_arena_allocator *arena, void *ptr)
{
(void)arena;
(void)ptr;
// 何もしない
}
/*
* 一括解放: O(n) チャンク数
*
* すべてのチャンクを一度に解放
*/
void arena_destroy(t_arena_allocator *arena)
{
t_arena *chunk = arena->first;
while (chunk) {
t_arena *next = chunk->next;
free(chunk->memory);
free(chunk);
chunk = next;
}
free(arena);
}
/*
* リセット: O(n) チャンク数
*
* メモリを解放せず、使用量をゼロにリセット
* 再利用パターンで効果的
*/
void arena_reset(t_arena_allocator *arena)
{
for (t_arena *chunk = arena->first; chunk; chunk = chunk->next) {
chunk->used = 0;
}
arena->current = arena->first;
}
アリーナの実践的実装
// arena.h - 実践的なアリーナアロケータ
#ifndef ARENA_H
# define ARENA_H
# include "libft.h"
# define ARENA_DEFAULT_SIZE 65536 // 64KB
# define ARENA_ALIGNMENT 16
typedef struct s_arena_chunk {
unsigned char *memory;
size_t capacity;
size_t used;
struct s_arena_chunk *next;
} t_arena_chunk;
typedef struct s_arena {
t_arena_chunk *first;
t_arena_chunk *current;
size_t default_chunk_size;
size_t total_allocated;
size_t chunk_count;
} t_arena;
// 基本操作
t_arena *arena_create(size_t default_chunk_size);
void *arena_alloc(t_arena *arena, size_t size);
void *arena_calloc(t_arena *arena, size_t count, size_t size);
void *arena_realloc(t_arena *arena, void *ptr,
size_t old_size, size_t new_size);
char *arena_strdup(t_arena *arena, const char *s);
void arena_reset(t_arena *arena);
void arena_destroy(t_arena *arena);
// 診断
typedef struct s_arena_stats {
size_t total_capacity;
size_t total_used;
size_t chunk_count;
float efficiency; // used / capacity
} t_arena_stats;
t_arena_stats arena_get_stats(t_arena *arena);
void arena_print_stats(t_arena *arena);
// スコープベースの一時アリーナ
typedef struct s_temp_arena {
t_arena *arena;
t_arena_chunk *saved_chunk;
size_t saved_used;
} t_temp_arena;
t_temp_arena temp_arena_begin(t_arena *arena);
void temp_arena_end(t_temp_arena temp);
#endif
// arena.c - 実装
#include "arena.h"
static t_arena_chunk *create_chunk(size_t size)
{
t_arena_chunk *chunk = malloc(sizeof(t_arena_chunk));
if (!chunk)
return NULL;
// 16バイトアラインメント
size = (size + ARENA_ALIGNMENT - 1) & ~(ARENA_ALIGNMENT - 1);
chunk->memory = aligned_alloc(ARENA_ALIGNMENT, size);
if (!chunk->memory) {
free(chunk);
return NULL;
}
chunk->capacity = size;
chunk->used = 0;
chunk->next = NULL;
return chunk;
}
t_arena *arena_create(size_t default_chunk_size)
{
t_arena *arena = malloc(sizeof(t_arena));
if (!arena)
return NULL;
arena->default_chunk_size = default_chunk_size > 0
? default_chunk_size
: ARENA_DEFAULT_SIZE;
arena->first = NULL;
arena->current = NULL;
arena->total_allocated = 0;
arena->chunk_count = 0;
return arena;
}
void *arena_alloc(t_arena *arena, size_t size)
{
if (!arena || size == 0)
return NULL;
// アラインメント調整
size = (size + ARENA_ALIGNMENT - 1) & ~(ARENA_ALIGNMENT - 1);
// 現在のチャンクに十分な空きがあるか
if (!arena->current ||
arena->current->used + size > arena->current->capacity) {
// 必要なサイズを計算(デフォルトより大きい場合は拡張)
size_t chunk_size = arena->default_chunk_size;
if (size > chunk_size)
chunk_size = size * 2; // 余裕を持たせる
t_arena_chunk *new_chunk = create_chunk(chunk_size);
if (!new_chunk)
return NULL;
// チェーンに追加
if (arena->current)
arena->current->next = new_chunk;
else
arena->first = new_chunk;
arena->current = new_chunk;
arena->chunk_count++;
}
// バンプポインタ方式で確保
void *ptr = arena->current->memory + arena->current->used;
arena->current->used += size;
arena->total_allocated += size;
return ptr;
}
void *arena_calloc(t_arena *arena, size_t count, size_t size)
{
size_t total = count * size;
void *ptr = arena_alloc(arena, total);
if (ptr)
ft_memset(ptr, 0, total);
return ptr;
}
char *arena_strdup(t_arena *arena, const char *s)
{
if (!s)
return NULL;
size_t len = ft_strlen(s) + 1;
char *copy = arena_alloc(arena, len);
if (copy)
ft_memcpy(copy, s, len);
return copy;
}
// 一時アリーナ: スコープ内での一時的な確保
// スコープ終了時に自動的にロールバック
t_temp_arena temp_arena_begin(t_arena *arena)
{
t_temp_arena temp;
temp.arena = arena;
temp.saved_chunk = arena->current;
temp.saved_used = arena->current ? arena->current->used : 0;
return temp;
}
void temp_arena_end(t_temp_arena temp)
{
// 保存した状態にロールバック
if (temp.saved_chunk) {
temp.arena->current = temp.saved_chunk;
temp.arena->current->used = temp.saved_used;
// 保存位置以降のチャンクを解放
t_arena_chunk *chunk = temp.saved_chunk->next;
temp.saved_chunk->next = NULL;
while (chunk) {
t_arena_chunk *next = chunk->next;
free(chunk->memory);
free(chunk);
temp.arena->chunk_count--;
chunk = next;
}
}
}
使用例:パーサーでのアリーナ活用
// JSON パーサーでのアリーナ活用例
typedef struct s_json_value {
int type;
union {
double number;
char *string;
struct {
struct s_json_value **items;
size_t count;
} array;
struct {
char **keys;
struct s_json_value **values;
size_t count;
} object;
};
} t_json_value;
// アリーナを使ったパース
t_json_value *json_parse(const char *input)
{
// パース用のアリーナを作成
t_arena *arena = arena_create(0); // デフォルトサイズ
if (!arena)
return NULL;
// パース処理(すべての確保はアリーナから)
t_json_value *root = parse_value(arena, input);
// 注意: rootとそのすべての子はarenaに確保されている
// arena_destroy()を呼ぶと全て無効になる
return root; // 呼び出し側がarenaも管理する必要あり
}
// より良い設計: 結果構造体にアリーナを含める
typedef struct s_json_document {
t_arena *arena;
t_json_value *root;
} t_json_document;
t_json_document *json_parse_document(const char *input)
{
t_json_document *doc = malloc(sizeof(t_json_document));
if (!doc)
return NULL;
doc->arena = arena_create(0);
if (!doc->arena) {
free(doc);
return NULL;
}
doc->root = parse_value(doc->arena, input);
return doc;
}
void json_free_document(t_json_document *doc)
{
if (doc) {
arena_destroy(doc->arena); // 全ノードを一括解放
free(doc);
}
}
---
9.4 プロトコル工学の基礎
9.4.1 通信プロトコルの理論
OSI参照モデル
国際標準化機構(ISO)が1984年に策定した通信プロトコルの階層モデル:
【OSI 7層モデル】
mermaid
block-beta
columns 1
L7["Layer 7: Application (HTTP, FTP, SMTP)"]
L6["Layer 6: Presentation (TLS, JPEG)"]
L5["Layer 5: Session (RPC)"]
L4["Layer 4: Transport (TCP, UDP)"]
L3["Layer 3: Network (IP)"]
L2["Layer 2: Data Link (Ethernet)"]
L1["Layer 1: Physical (Cables)"]
style L7 fill:#f9f
style L6 fill:#f9f
style L5 fill:#f9f
style L4 fill:#ff9
style L3 fill:#ff9
style L2 fill:#9f9
style L1 fill:#9f9
各層の独立性:
- 上位層は下位層のサービスを利用
- 下位層の実装詳細を知る必要なし
- 同じ層のプロトコルは置き換え可能
HTTPプロトコルの構造
HTTP(Hypertext Transfer Protocol)は、OSI第7層(アプリケーション層)のプロトコル。
【HTTPリクエストの構造】
mermaid
packet-beta
title HTTP Request
0-31: "Method (GET)"
32-63: "URI (/path/to/resource)"
64-95: "Version (HTTP/1.1)"
96-127: "Headers (Host, User-Agent, ...)"
128-159: "Empty Line (CRLF)"
160-191: "Message Body (Optional)"
【HTTPレスポンスの構造】
mermaid
packet-beta
title HTTP Response
0-31: "Version (HTTP/1.1)"
32-47: "Code (200)"
48-63: "Reason (OK)"
64-95: "Headers (Content-Type, ...)"
96-127: "Empty Line (CRLF)"
128-159: "Message Body (HTML)"
9.4.2 パーサー設計の原理
有限オートマトン理論
プロトコルパーサーは、有限オートマトン(Finite Automaton)として設計できる。
【HTTPリクエストパーサーのステートマシン】
mermaid
stateDiagram-v2
[] --> PARSE_METHOD
PARSE_METHOD --> PARSE_URI: Space
PARSE_URI --> PARSE_VERSION: Space
PARSE_VERSION --> PARSE_HEADER: CRLF
state PARSE_HEADER {
[] --> CHECK_LINE
CHECK_LINE --> HEADER_NAME: Char
CHECK_LINE --> CHECK_BODY: CRLF (Empty Line)
HEADER_NAME --> HEADER_VALUE: Colon
HEADER_VALUE --> CHECK_LINE: CRLF
}
PARSE_HEADER --> PARSE_BODY: Content-Length > 0
PARSE_HEADER --> COMPLETE: No Body
PARSE_BODY --> COMPLETE: Body Length Reached
COMPLETE --> [*]
状態遷移ベースのパーサー実装
// http_parser.h - 状態機械ベースのHTTPパーサー
#ifndef HTTP_PARSER_H
# define HTTP_PARSER_H
# include "libft.h"
// パーサー状態
typedef enum e_parser_state {
PARSE_METHOD,
PARSE_URI,
PARSE_VERSION,
PARSE_HEADER_NAME,
PARSE_HEADER_VALUE,
PARSE_BODY,
PARSE_COMPLETE,
PARSE_ERROR
} t_parser_state;
// HTTPメソッド
typedef enum e_http_method {
HTTP_GET,
HTTP_POST,
HTTP_PUT,
HTTP_DELETE,
HTTP_HEAD,
HTTP_OPTIONS,
HTTP_UNKNOWN
} t_http_method;
// HTTPヘッダー
typedef struct s_http_header {
char *name;
char *value;
struct s_http_header *next;
} t_http_header;
// HTTPリクエスト
typedef struct s_http_request {
t_http_method method;
char *uri;
char *query_string;
int version_major;
int version_minor;
t_http_header *headers;
char *body;
size_t body_length;
size_t content_length;
} t_http_request;
// パーサーコンテキスト
typedef struct s_http_parser {
t_parser_state state;
t_http_request *request;
// 一時バッファ
char *buffer;
size_t buffer_size;
size_t buffer_pos;
// 現在の解析位置
char *current_header_name;
// エラー情報
int error_code;
const char *error_message;
} t_http_parser;
// パーサー操作
t_http_parser *http_parser_create(void);
int http_parser_feed(t_http_parser *parser,
const char *data, size_t len);
t_http_request *http_parser_get_request(t_http_parser *parser);
void http_parser_reset(t_http_parser *parser);
void http_parser_destroy(t_http_parser *parser);
// ヘルパー関数
const char *http_method_str(t_http_method method);
t_http_method http_method_from_str(const char *str);
const char *http_status_str(int code);
#endif
// http_parser.c
#include "http_parser.h"
#include <ctype.h>
// 文字分類の最適化(ルックアップテーブル)
static const unsigned char token_char[256] = {
['A'...'Z'] = 1, ['a'...'z'] = 1,
['0'...'9'] = 1,
['!'] = 1, ['#'] = 1, ['---
9.5 データ構造設計の原理
9.5.1 抽象データ型(ADT)の理論
ADTの概念
抽象データ型は、Barbara Liskovが1974年に提唱した概念。データの操作を、実装から分離した抽象として定義する。
【抽象データ型の定義】
ADT = (D, F, A)
D: ドメイン(データの集合)
F: 操作の集合(関数シグネチャ)
A: 公理の集合(操作の性質)
【例: スタックのADT】
Domain: Stack<T>(型Tの要素を持つスタック)
Operations:
- new() → Stack<T> // 空のスタックを作成
- push(Stack<T>, T) → Stack<T> // 要素を追加
- pop(Stack<T>) → (Stack<T>, T) // 要素を取り出し
- peek(Stack<T>) → T // トップを参照
- is_empty(Stack<T>) → bool // 空かどうか
Axioms:
1. is_empty(new()) = true
2. is_empty(push(s, x)) = false
3. pop(push(s, x)) = (s, x)
4. peek(push(s, x)) = x
実装の自由:
- 配列ベース
- リンクリストベース
- 内部構造は外部から見えない
キー・バリューストアのADT
// kvstore.h - Key-Valueストアの抽象データ型
#ifndef KVSTORE_H
# define KVSTORE_H
# include "libft.h"
/*
* Key-Value Store ADT
*
* Domain: KVStore(文字列キーから任意値へのマッピング)
*
* Operations:
* - create() → KVStore
* - put(KVStore, string, value) → bool
* - get(KVStore, string) → value | null
* - delete(KVStore, string) → bool
* - contains(KVStore, string) → bool
* - size(KVStore) → int
* - destroy(KVStore) → void
*
* Axioms:
* 1. get(put(s, k, v), k) = v
* 2. get(put(s, k, v), k') = get(s, k') where k ≠ k'
* 3. contains(put(s, k, v), k) = true
* 4. size(put(s, k, v)) = size(s) + 1 if !contains(s, k)
* 5. size(delete(s, k)) = size(s) - 1 if contains(s, k)
*/
// 不透明な型(実装隠蔽)
typedef struct s_kvstore t_kvstore;
// イテレータ
typedef struct s_kv_iterator t_kv_iterator;
// 基本操作
t_kvstore *kvstore_create(size_t initial_capacity);
void kvstore_destroy(t_kvstore *store);
int kvstore_put(t_kvstore *store, const char *key,
const void *value, size_t value_size);
void *kvstore_get(t_kvstore *store, const char *key, size_t *size);
int kvstore_delete(t_kvstore *store, const char *key);
int kvstore_contains(t_kvstore *store, const char *key);
size_t kvstore_size(t_kvstore *store);
// イテレータ
t_kv_iterator *kvstore_iterator_create(t_kvstore *store);
int kvstore_iterator_next(t_kv_iterator *iter,
const char **key,
void **value, size_t *size);
void kvstore_iterator_destroy(t_kv_iterator *iter);
// 永続化
int kvstore_save(t_kvstore *store, const char *filename);
t_kvstore *kvstore_load(const char *filename);
#endif
9.5.2 ハッシュテーブルの理論と実装
ハッシュ関数の理論
良いハッシュ関数は以下の性質を持つ:
- 決定性:同じ入力には常に同じ出力
- 均一分布:出力が均等に分布
- 雪崩効果:入力の小さな変化が出力を大きく変える
- 効率性:計算が高速
// ハッシュ関数の比較
// 1. 単純な加算ハッシュ(悪い例)
size_t bad_hash(const char *key)
{
size_t hash = 0;
while (*key)
hash += *key++; // 順序が考慮されない
return hash;
}
// 問題: "abc"と"cba"が同じハッシュ値
// 2. DJB2ハッシュ(Dan Bernstein)
size_t djb2_hash(const char *key)
{
size_t hash = 5381;
int c;
while ((c = *key++))
hash = ((hash << 5) + hash) + c; // hash * 33 + c
return hash;
}
// 特徴: シンプルで高速、良好な分布
// 3. FNV-1aハッシュ(Fowler-Noll-Vo)
size_t fnv1a_hash(const char *key)
{
size_t hash = 14695981039346656037ULL; // FNV offset basis
while (*key) {
hash ^= (unsigned char)*key++;
hash *= 1099511628211ULL; // FNV prime
}
return hash;
}
// 特徴: 衝突が少ない、良好な雪崩効果
// 4. MurmurHash3(Austin Appleby)
// 非暗号学的ハッシュの中で最も優れた性能
// 詳細な実装は複雑なため省略
衝突解決戦略
【チェイン法(Separate Chaining)】
ハッシュテーブル:
┌────┐
│ 0 │ → [A] → [D] → NULL
├────┤
│ 1 │ → NULL
├────┤
│ 2 │ → [B] → NULL
├────┤
│ 3 │ → [C] → [E] → [F] → NULL
├────┤
│ 4 │ → NULL
└────┘
利点:
- シンプルな実装
- 負荷率 > 1 でも動作
- 削除が容易
欠点:
- ポインタのオーバーヘッド
- キャッシュ効率が悪い
【オープンアドレス法(Open Addressing)】
ハッシュテーブル(Linear Probing):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ A │ D │ │ B │ C │ E │ F │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑ ↑
hash(A)=0 hash(B)=3
hash(D)=0 → 1 hash(C)=3 → 4
hash(E)=3 → 5
hash(F)=3 → 6
利点:
- キャッシュ効率が良い
- メモリオーバーヘッドが少ない
欠点:
- クラスタリング問題
- 削除が複雑(墓石マーカー必要)
- 負荷率に敏感
ハッシュテーブルの実装
// kvstore_impl.c - チェイン法によるKVストア実装
#include "kvstore.h"
#include <pthread.h>
// エントリ構造体
typedef struct s_kv_entry {
char *key;
void *value;
size_t value_size;
struct s_kv_entry *next;
} t_kv_entry;
// KVストア構造体
struct s_kvstore {
t_kv_entry **buckets;
size_t bucket_count;
size_t entry_count;
size_t threshold; // リサイズ閾値
float load_factor; // 最大負荷率
pthread_rwlock_t lock; // 読み書きロック
};
// イテレータ構造体
struct s_kv_iterator {
t_kvstore *store;
size_t bucket_index;
t_kv_entry *current;
};
// ハッシュ関数
static size_t hash_key(const char *key, size_t bucket_count)
{
size_t hash = 5381;
int c;
while ((c = *key++))
hash = ((hash << 5) + hash) + c;
return hash % bucket_count;
}
// 作成
t_kvstore *kvstore_create(size_t initial_capacity)
{
t_kvstore *store = ft_calloc(1, sizeof(t_kvstore));
if (!store)
return NULL;
store->bucket_count = initial_capacity > 0 ? initial_capacity : 16;
store->buckets = ft_calloc(store->bucket_count, sizeof(t_kv_entry*));
if (!store->buckets) {
free(store);
return NULL;
}
store->load_factor = 0.75f;
store->threshold = (size_t)(store->bucket_count * store->load_factor);
pthread_rwlock_init(&store->lock, NULL);
return store;
}
// リサイズ
static int kvstore_resize(t_kvstore *store)
{
size_t new_bucket_count = store->bucket_count * 2;
t_kv_entry **new_buckets = ft_calloc(new_bucket_count, sizeof(t_kv_entry*));
if (!new_buckets)
return -1;
// 全エントリを再ハッシュ
for (size_t i = 0; i < store->bucket_count; i++) {
t_kv_entry *entry = store->buckets[i];
while (entry) {
t_kv_entry *next = entry->next;
size_t new_index = hash_key(entry->key, new_bucket_count);
entry->next = new_buckets[new_index];
new_buckets[new_index] = entry;
entry = next;
}
}
free(store->buckets);
store->buckets = new_buckets;
store->bucket_count = new_bucket_count;
store->threshold = (size_t)(store->bucket_count * store->load_factor);
return 0;
}
// 挿入
int kvstore_put(t_kvstore *store, const char *key,
const void *value, size_t value_size)
{
if (!store || !key || !value || value_size == 0)
return -1;
pthread_rwlock_wrlock(&store->lock);
// リサイズチェック
if (store->entry_count >= store->threshold) {
if (kvstore_resize(store) < 0) {
pthread_rwlock_unlock(&store->lock);
return -1;
}
}
size_t index = hash_key(key, store->bucket_count);
// 既存エントリの検索
for (t_kv_entry *e = store->buckets[index]; e; e = e->next) {
if (ft_strcmp(e->key, key) == 0) {
// 更新
void *new_value = malloc(value_size);
if (!new_value) {
pthread_rwlock_unlock(&store->lock);
return -1;
}
free(e->value);
ft_memcpy(new_value, value, value_size);
e->value = new_value;
e->value_size = value_size;
pthread_rwlock_unlock(&store->lock);
return 0;
}
}
// 新規エントリ
t_kv_entry *entry = malloc(sizeof(t_kv_entry));
if (!entry) {
pthread_rwlock_unlock(&store->lock);
return -1;
}
entry->key = ft_strdup(key);
entry->value = malloc(value_size);
if (!entry->key || !entry->value) {
free(entry->key);
free(entry->value);
free(entry);
pthread_rwlock_unlock(&store->lock);
return -1;
}
ft_memcpy(entry->value, value, value_size);
entry->value_size = value_size;
entry->next = store->buckets[index];
store->buckets[index] = entry;
store->entry_count++;
pthread_rwlock_unlock(&store->lock);
return 0;
}
// 取得
void *kvstore_get(t_kvstore *store, const char *key, size_t *size)
{
if (!store || !key)
return NULL;
pthread_rwlock_rdlock(&store->lock);
size_t index = hash_key(key, store->bucket_count);
for (t_kv_entry *e = store->buckets[index]; e; e = e->next) {
if (ft_strcmp(e->key, key) == 0) {
void *copy = malloc(e->value_size);
if (copy) {
ft_memcpy(copy, e->value, e->value_size);
if (size)
*size = e->value_size;
}
pthread_rwlock_unlock(&store->lock);
return copy;
}
}
pthread_rwlock_unlock(&store->lock);
return NULL;
}
// 削除
int kvstore_delete(t_kvstore *store, const char *key)
{
if (!store || !key)
return -1;
pthread_rwlock_wrlock(&store->lock);
size_t index = hash_key(key, store->bucket_count);
t_kv_entry **pp = &store->buckets[index];
while (*pp) {
if (ft_strcmp((*pp)->key, key) == 0) {
t_kv_entry *to_delete = *pp;
*pp = to_delete->next;
free(to_delete->key);
free(to_delete->value);
free(to_delete);
store->entry_count--;
pthread_rwlock_unlock(&store->lock);
return 0;
}
pp = &(*pp)->next;
}
pthread_rwlock_unlock(&store->lock);
return -1; // キーが見つからない
}
// 永続化
int kvstore_save(t_kvstore *store, const char *filename)
{
FILE *fp = fopen(filename, "wb");
if (!fp)
return -1;
pthread_rwlock_rdlock(&store->lock);
// ヘッダー: マジックナンバー + バージョン + エントリ数
uint32_t magic = 0x4B565354; // "KVST"
uint32_t version = 1;
uint64_t count = store->entry_count;
fwrite(&magic, sizeof(magic), 1, fp);
fwrite(&version, sizeof(version), 1, fp);
fwrite(&count, sizeof(count), 1, fp);
// エントリを書き込み
for (size_t i = 0; i < store->bucket_count; i++) {
for (t_kv_entry *e = store->buckets[i]; e; e = e->next) {
uint32_t key_len = ft_strlen(e->key);
uint64_t value_size = e->value_size;
fwrite(&key_len, sizeof(key_len), 1, fp);
fwrite(e->key, key_len, 1, fp);
fwrite(&value_size, sizeof(value_size), 1, fp);
fwrite(e->value, e->value_size, 1, fp);
}
}
pthread_rwlock_unlock(&store->lock);
fclose(fp);
return 0;
}
// 読み込み
t_kvstore *kvstore_load(const char *filename)
{
FILE *fp = fopen(filename, "rb");
if (!fp)
return NULL;
// ヘッダーの読み込みと検証
uint32_t magic, version;
uint64_t count;
if (fread(&magic, sizeof(magic), 1, fp) != 1 ||
fread(&version, sizeof(version), 1, fp) != 1 ||
fread(&count, sizeof(count), 1, fp) != 1) {
fclose(fp);
return NULL;
}
if (magic != 0x4B565354 || version != 1) {
fclose(fp);
return NULL;
}
// ストアを作成
t_kvstore *store = kvstore_create(count * 2); // 余裕を持たせる
if (!store) {
fclose(fp);
return NULL;
}
// エントリを読み込み
for (uint64_t i = 0; i < count; i++) {
uint32_t key_len;
uint64_t value_size;
if (fread(&key_len, sizeof(key_len), 1, fp) != 1)
break;
char *key = malloc(key_len + 1);
if (!key || fread(key, key_len, 1, fp) != 1) {
free(key);
break;
}
key[key_len] = '\0';
if (fread(&value_size, sizeof(value_size), 1, fp) != 1) {
free(key);
break;
}
void *value = malloc(value_size);
if (!value || fread(value, value_size, 1, fp) != 1) {
free(key);
free(value);
break;
}
kvstore_put(store, key, value, value_size);
free(key);
free(value);
}
fclose(fp);
return store;
}
---
9.6 イベント駆動アーキテクチャ
9.6.1 I/Oモデルの進化
同期 vs 非同期I/O
【ブロッキング(同期)I/O】
スレッド1:
┌─────────────────────────────────────────────────────────┐
│ 処理 │ read()でブロック │ データ処理 │ 処理 │
└─────────────────────────────────────────────────────────┘
↓
この間何もできない
問題:
- スレッドが待機状態で無駄
- 多数の接続にはスレッドプールが必要
- スレッド数 = 同時接続数(スケールしない)
【ノンブロッキング(非同期)I/O】
スレッド1:
┌─────────────────────────────────────────────────────────┐
│ 処理 │fd1確認│fd2確認│fd3確認│fd1処理│fd2処理│fd3処理 │
└─────────────────────────────────────────────────────────┘
↓ ↓ ↓
準備なし 準備OK 準備なし
(スキップ) (スキップ)
利点:
- 1スレッドで多数の接続を処理可能
- リソース効率が高い
- C10K問題の解決策
I/O多重化の進化
【select() - 1983年 BSD 4.2】
制限:
- 最大1024ファイルディスクリプタ
- O(n)でファイルディスクリプタをスキャン
- 呼び出しごとにfd_setをコピー
【poll() - 1986年 System V】
改善:
- ファイルディスクリプタ数の制限なし
- より効率的なAPI
残る問題:
- 依然O(n)スキャン
【epoll() - 2002年 Linux 2.5.44】
革新:
- O(1)でイベント取得
- Edge-triggeredモード
- ファイルディスクリプタをカーネル側で管理
【kqueue() - 2000年 FreeBSD 4.1】
特徴:
- epollと同様の性能
- より汎用的(ファイル変更、シグナル等も監視可能)
9.6.2 Reactorパターン
Douglas Schmidtが1995年に体系化したデザインパターン。
【Reactorパターンの構造】
mermaid
graph TD
subgraph Reactor
Demuxer[Event Demuxer (epoll/kqueue)]
Dispatcher[Event Dispatcher]
Registry[Handler Registry]
Demuxer -->|Events| Dispatcher
Dispatcher -->|Callback| Registry
Registry -->|fd1| AcceptHandler
Registry -->|fd2| ReadHandler
Registry -->|fd3| WriteHandler
end
subgraph Handlers
AcceptHandler
ReadHandler
WriteHandler
TimerHandler
end
Dispatcher --> Handlers
Reactorパターンの実装
// reactor.h - Reactorパターンの実装
#ifndef REACTOR_H
# define REACTOR_H
# include "libft.h"
# ifdef __linux__
# include <sys/epoll.h>
# else
# include <sys/event.h>
# endif
// イベントタイプ
typedef enum e_event_type {
EVENT_READ = 1 << 0,
EVENT_WRITE = 1 << 1,
EVENT_ERROR = 1 << 2,
EVENT_CLOSE = 1 << 3
} t_event_type;
// 前方宣言
typedef struct s_reactor t_reactor;
// イベントハンドラ
typedef void (*t_event_handler)(t_reactor *reactor, int fd,
t_event_type events, void *user_data);
// ハンドラ登録情報
typedef struct s_handler_info {
int fd;
t_event_type events;
t_event_handler handler;
void *user_data;
} t_handler_info;
// Reactor構造体
struct s_reactor {
#ifdef __linux__
int epoll_fd;
#else
int kqueue_fd;
#endif
t_handler_info **handlers;
size_t handler_count;
size_t handler_capacity;
int running;
};
// Reactor操作
t_reactor *reactor_create(void);
void reactor_destroy(t_reactor *reactor);
int reactor_register(t_reactor *reactor, int fd,
t_event_type events,
t_event_handler handler,
void *user_data);
int reactor_modify(t_reactor *reactor, int fd,
t_event_type new_events);
int reactor_unregister(t_reactor *reactor, int fd);
int reactor_run(t_reactor *reactor);
void reactor_stop(t_reactor *reactor);
#endif
// reactor.c - Linux (epoll) 実装
#include "reactor.h"
#include <unistd.h>
#include <errno.h>
#ifdef __linux__
t_reactor *reactor_create(void)
{
t_reactor *reactor = ft_calloc(1, sizeof(t_reactor));
if (!reactor)
return NULL;
reactor->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (reactor->epoll_fd < 0) {
free(reactor);
return NULL;
}
reactor->handler_capacity = 64;
reactor->handlers = ft_calloc(reactor->handler_capacity,
sizeof(t_handler_info*));
if (!reactor->handlers) {
close(reactor->epoll_fd);
free(reactor);
return NULL;
}
return reactor;
}
static t_handler_info *find_handler(t_reactor *reactor, int fd)
{
for (size_t i = 0; i < reactor->handler_count; i++) {
if (reactor->handlers[i] && reactor->handlers[i]->fd == fd)
return reactor->handlers[i];
}
return NULL;
}
static uint32_t events_to_epoll(t_event_type events)
{
uint32_t epoll_events = 0;
if (events & EVENT_READ)
epoll_events |= EPOLLIN;
if (events & EVENT_WRITE)
epoll_events |= EPOLLOUT;
return epoll_events | EPOLLET; // Edge-triggered
}
static t_event_type epoll_to_events(uint32_t epoll_events)
{
t_event_type events = 0;
if (epoll_events & EPOLLIN)
events |= EVENT_READ;
if (epoll_events & EPOLLOUT)
events |= EVENT_WRITE;
if (epoll_events & EPOLLERR)
events |= EVENT_ERROR;
if (epoll_events & EPOLLHUP)
events |= EVENT_CLOSE;
return events;
}
int reactor_register(t_reactor *reactor, int fd,
t_event_type events,
t_event_handler handler,
void *user_data)
{
// 既存チェック
if (find_handler(reactor, fd))
return -1;
// 容量拡張
if (reactor->handler_count >= reactor->handler_capacity) {
size_t new_cap = reactor->handler_capacity * 2;
t_handler_info **new_handlers = realloc(reactor->handlers,
new_cap * sizeof(t_handler_info*));
if (!new_handlers)
return -1;
reactor->handlers = new_handlers;
reactor->handler_capacity = new_cap;
}
// ハンドラ情報作成
t_handler_info *info = malloc(sizeof(t_handler_info));
if (!info)
return -1;
info->fd = fd;
info->events = events;
info->handler = handler;
info->user_data = user_data;
// epollに登録
struct epoll_event ev;
ev.events = events_to_epoll(events);
ev.data.ptr = info;
if (epoll_ctl(reactor->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
free(info);
return -1;
}
reactor->handlers[reactor->handler_count++] = info;
return 0;
}
int reactor_unregister(t_reactor *reactor, int fd)
{
for (size_t i = 0; i < reactor->handler_count; i++) {
if (reactor->handlers[i] && reactor->handlers[i]->fd == fd) {
epoll_ctl(reactor->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
free(reactor->handlers[i]);
reactor->handlers[i] = reactor->handlers[--reactor->handler_count];
reactor->handlers[reactor->handler_count] = NULL;
return 0;
}
}
return -1;
}
int reactor_run(t_reactor *reactor)
{
struct epoll_event events[256];
reactor->running = 1;
while (reactor->running) {
int n = epoll_wait(reactor->epoll_fd, events, 256, 1000);
if (n < 0) {
if (errno == EINTR)
continue;
return -1;
}
for (int i = 0; i < n; i++) {
t_handler_info *info = events[i].data.ptr;
t_event_type ev_type = epoll_to_events(events[i].events);
if (info->handler) {
info->handler(reactor, info->fd, ev_type, info->user_data);
}
}
}
return 0;
}
void reactor_stop(t_reactor *reactor)
{
reactor->running = 0;
}
void reactor_destroy(t_reactor *reactor)
{
if (!reactor)
return;
for (size_t i = 0; i < reactor->handler_count; i++) {
free(reactor->handlers[i]);
}
free(reactor->handlers);
close(reactor->epoll_fd);
free(reactor);
}
#endif
エコーサーバーの実装例
// echo_server.c - Reactorパターンを使ったエコーサーバー
#include "reactor.h"
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct s_connection {
int fd;
char buffer[4096];
size_t len;
} t_connection;
static void set_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
// 読み込みイベントハンドラ
static void on_read(t_reactor *reactor, int fd, t_event_type events, void *user_data)
{
t_connection *conn = user_data;
(void)events;
// 読み込み
ssize_t n = read(fd, conn->buffer + conn->len,
sizeof(conn->buffer) - conn->len - 1);
if (n <= 0) {
// 接続終了またはエラー
reactor_unregister(reactor, fd);
close(fd);
free(conn);
return;
}
conn->len += n;
conn->buffer[conn->len] = '\0';
// エコーバック
write(fd, conn->buffer, conn->len);
conn->len = 0;
}
// 新規接続ハンドラ
static void on_accept(t_reactor *reactor, int fd, t_event_type events, void *user_data)
{
(void)events;
(void)user_data;
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
while (1) {
int client_fd = accept(fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0)
break;
set_nonblocking(client_fd);
t_connection *conn = ft_calloc(1, sizeof(t_connection));
if (!conn) {
close(client_fd);
continue;
}
conn->fd = client_fd;
reactor_register(reactor, client_fd, EVENT_READ, on_read, conn);
}
}
int main(void)
{
// サーバーソケット作成
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0)
return 1;
int reuse = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
set_nonblocking(server_fd);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = INADDR_ANY
};
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
return 1;
if (listen(server_fd, SOMAXCONN) < 0)
return 1;
// Reactor作成・実行
t_reactor *reactor = reactor_create();
if (!reactor)
return 1;
reactor_register(reactor, server_fd, EVENT_READ, on_accept, NULL);
reactor_run(reactor);
reactor_destroy(reactor);
close(server_fd);
return 0;
}
---
9.7 Libftの統合パターン
9.7.1 実践的な統合例
コマンドラインツールへの統合
// cli_tool.c - Libftを使ったCLIツールの例
#include "libft.h"
#include "arena.h"
typedef struct s_options {
int verbose;
int force;
char *input_file;
char *output_file;
char **extra_args;
int extra_count;
} t_options;
// 引数解析
t_options *parse_args(t_arena *arena, int argc, char **argv)
{
t_options *opts = arena_calloc(arena, 1, sizeof(t_options));
if (!opts)
return NULL;
int i = 1;
while (i < argc) {
if (ft_strcmp(argv[i], "-v") == 0 ||
ft_strcmp(argv[i], "--verbose") == 0) {
opts->verbose = 1;
}
else if (ft_strcmp(argv[i], "-f") == 0 ||
ft_strcmp(argv[i], "--force") == 0) {
opts->force = 1;
}
else if (ft_strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
opts->input_file = arena_strdup(arena, argv[++i]);
}
else if (ft_strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
opts->output_file = arena_strdup(arena, argv[++i]);
}
else if (argv[i][0] != '-') {
// 追加引数
opts->extra_count++;
}
i++;
}
// 追加引数を収集
if (opts->extra_count > 0) {
opts->extra_args = arena_calloc(arena, opts->extra_count, sizeof(char*));
int j = 0;
for (i = 1; i < argc && j < opts->extra_count; i++) {
if (argv[i][0] != '-' &&
(i < 2 || (ft_strcmp(argv[i-1], "-i") != 0 &&
ft_strcmp(argv[i-1], "-o") != 0))) {
opts->extra_args[j++] = arena_strdup(arena, argv[i]);
}
}
}
return opts;
}
int main(int argc, char **argv)
{
// 全体のメモリ管理にアリーナを使用
t_arena *arena = arena_create(0);
if (!arena)
return 1;
// 引数解析
t_options *opts = parse_args(arena, argc, argv);
if (!opts) {
arena_destroy(arena);
return 1;
}
if (opts->verbose) {
ft_putstr_fd("Input: ", 1);
ft_putendl_fd(opts->input_file ? opts->input_file : "(stdin)", 1);
ft_putstr_fd("Output: ", 1);
ft_putendl_fd(opts->output_file ? opts->output_file : "(stdout)", 1);
}
// 処理...
// 終了時に一括解放
arena_destroy(arena);
return 0;
}
9.7.2 プロジェクト統合のMakefile
# Makefile - 実践的なプロジェクト統合
# プロジェクト情報
NAME = myproject
VERSION = 1.0.0
# ディレクトリ構成
SRC_DIR = src
INC_DIR = include
OBJ_DIR = obj
LIB_DIR = lib
BIN_DIR = bin
# Libftの統合
LIBFT_DIR = libft
LIBFT = $(LIBFT_DIR)/libft.a
LIBFT_INC = $(LIBFT_DIR)/include
# コンパイラ設定
CC = cc
CFLAGS = -Wall -Wextra -Werror -std=c99
CFLAGS += -I$(INC_DIR) -I$(LIBFT_INC)
# ビルドモード
ifeq ($(DEBUG), 1)
CFLAGS += -g3 -O0 -DDEBUG
CFLAGS += -fsanitize=address,undefined
LDFLAGS += -fsanitize=address,undefined
else
CFLAGS += -O3 -DNDEBUG
endif
# ソースファイル
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# デフォルトターゲット
all: $(BIN_DIR)/$(NAME)
# Libftのビルド
$(LIBFT):
@$(MAKE) -C $(LIBFT_DIR)
# メインバイナリ
$(BIN_DIR)/$(NAME): $(OBJS) $(LIBFT)
@mkdir -p $(BIN_DIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) -L$(LIBFT_DIR) -lft
# オブジェクトファイル
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(OBJ_DIR)
$(CC) $(CFLAGS) -c ___CODE_BLOCK_47___lt; -o $@
# クリーン
clean:
rm -rf $(OBJ_DIR)
@$(MAKE) -C $(LIBFT_DIR) clean
fclean: clean
rm -rf $(BIN_DIR)
@$(MAKE) -C $(LIBFT_DIR) fclean
re: fclean all
# テスト
test: all
@echo "Running tests..."
@./$(BIN_DIR)/$(NAME) --test
.PHONY: all clean fclean re test
---
まとめ
この章では、ソフトウェアエンジニアリングの原理と、Libftを実世界のアプリケーションに統合する方法を学びました。
学んだ理論的基盤:
- モジュール性の理論
- Parnasの情報隠蔽原理
- 結合度と凝集度の評価
- 契約による設計(Design by Contract)- ビルドシステムの原理
- 分割コンパイルモデル
- 依存関係グラフとDAG
- 静的・動的リンクの理論- メモリアロケータ設計
- フラグメンテーション問題
- アロケータアルゴリズム
- メモリプールとアリーナアロケータ- プロトコル工学
- OSI参照モデル
- 状態機械ベースのパーサー設計
- HTTPプロトコルの構造- データ構造設計
- 抽象データ型(ADT)
- ハッシュテーブルの理論と実装- イベント駆動アーキテクチャ
- I/Oモデルの進化
- Reactorパターンこれらの原理を理解することで、Libftの関数群を効果的に活用し、堅牢でスケーラブルなアプリケーションを構築できます。
] = 1, ['%'] = 1,
['&'] = 1, ['\''] = 1, ['*'] = 1, ['+'] = 1,
['-'] = 1, ['.'] = 1, ['^'] = 1, ['_'] = 1,
['`'] = 1, ['|'] = 1, ['~'] = 1,
};
static inline int is_token_char(unsigned char c)
{
return token_char[c];
}
t_http_parser *http_parser_create(void)
{
t_http_parser *parser = ft_calloc(1, sizeof(t_http_parser));
if (!parser)
return NULL;
parser->request = ft_calloc(1, sizeof(t_http_request));
if (!parser->request) {
free(parser);
return NULL;
}
parser->buffer_size = 4096;
parser->buffer = malloc(parser->buffer_size);
if (!parser->buffer) {
free(parser->request);
free(parser);
return NULL;
}
parser->state = PARSE_METHOD;
return parser;
}
// メソッド解析
static int parse_method(t_http_parser *parser, const char **pp, const char *end)
{
const char *p = *pp;
const char *start = p;
// トークン文字を読み進める
while (p < end && is_token_char(*p))
p++;
if (p == start) {
parser->error_message = "Empty method";
return -1;
}
if (p >= end)
return 0; // もっとデータが必要
if (*p != ' ') {
parser->error_message = "Expected space after method";
return -1;
}
// メソッドを設定
size_t len = p - start;
if (len == 3 && ft_memcmp(start, "GET", 3) == 0)
parser->request->method = HTTP_GET;
else if (len == 4 && ft_memcmp(start, "POST", 4) == 0)
parser->request->method = HTTP_POST;
else if (len == 3 && ft_memcmp(start, "PUT", 3) == 0)
parser->request->method = HTTP_PUT;
else if (len == 6 && ft_memcmp(start, "DELETE", 6) == 0)
parser->request->method = HTTP_DELETE;
else if (len == 4 && ft_memcmp(start, "HEAD", 4) == 0)
parser->request->method = HTTP_HEAD;
else if (len == 7 && ft_memcmp(start, "OPTIONS", 7) == 0)
parser->request->method = HTTP_OPTIONS;
else
parser->request->method = HTTP_UNKNOWN;
parser->state = PARSE_URI;
*pp = p + 1; // 空白をスキップ
return 1;
}
// URI解析
static int parse_uri(t_http_parser *parser, const char **pp, const char *end)
{
const char *p = *pp;
const char *start = p;
const char *query = NULL;
// URIの終端(空白)まで読み進める
while (p < end && *p != ' ' && *p != '\r') {
if (*p == '?' && !query)
query = p;
p++;
}
if (p >= end)
return 0; // もっとデータが必要
if (*p != ' ') {
parser->error_message = "Expected space after URI";
return -1;
}
// URIを設定
size_t uri_len = query ? (size_t)(query - start) : (size_t)(p - start);
parser->request->uri = malloc(uri_len + 1);
if (!parser->request->uri)
return -1;
ft_memcpy(parser->request->uri, start, uri_len);
parser->request->uri[uri_len] = '\0';
// クエリ文字列を設定
if (query) {
size_t query_len = p - query - 1;
parser->request->query_string = malloc(query_len + 1);
if (parser->request->query_string) {
ft_memcpy(parser->request->query_string, query + 1, query_len);
parser->request->query_string[query_len] = '\0';
}
}
parser->state = PARSE_VERSION;
*pp = p + 1;
return 1;
}
// バージョン解析
static int parse_version(t_http_parser *parser, const char **pp, const char *end)
{
const char *p = *pp;
// "HTTP/X.X\r\n" を期待
if (end - p < 10)
return 0; // もっとデータが必要
if (ft_memcmp(p, "HTTP/", 5) != 0) {
parser->error_message = "Invalid HTTP version";
return -1;
}
p += 5;
if (!isdigit(*p)) {
parser->error_message = "Invalid major version";
return -1;
}
parser->request->version_major = *p++ - '0';
if (*p++ != '.') {
parser->error_message = "Expected dot in version";
return -1;
}
if (!isdigit(*p)) {
parser->error_message = "Invalid minor version";
return -1;
}
parser->request->version_minor = *p++ - '0';
if (*p++ != '\r' || *p++ != '\n') {
parser->error_message = "Expected CRLF after version";
return -1;
}
parser->state = PARSE_HEADER_NAME;
*pp = p;
return 1;
}
// ヘッダー名解析
static int parse_header_name(t_http_parser *parser, const char **pp, const char *end)
{
const char *p = *pp;
// 空行チェック(ヘッダー終了)
if (end - p >= 2 && *p == '\r' && *(p + 1) == '\n') {
// Content-Lengthがあればボディを読む
for (t_http_header *h = parser->request->headers; h; h = h->next) {
if (ft_strcmp(h->name, "Content-Length") == 0) {
parser->request->content_length = ft_atoi(h->value);
break;
}
}
if (parser->request->content_length > 0) {
parser->state = PARSE_BODY;
} else {
parser->state = PARSE_COMPLETE;
}
*pp = p + 2;
return 1;
}
// ヘッダー名を読む
const char *start = p;
while (p < end && is_token_char(*p))
p++;
if (p >= end)
return 0;
if (*p != ':') {
parser->error_message = "Expected colon after header name";
return -1;
}
// ヘッダー名を保存
size_t len = p - start;
parser->current_header_name = malloc(len + 1);
if (!parser->current_header_name)
return -1;
ft_memcpy(parser->current_header_name, start, len);
parser->current_header_name[len] = '\0';
parser->state = PARSE_HEADER_VALUE;
*pp = p + 1;
return 1;
}
// ヘッダー値解析
static int parse_header_value(t_http_parser *parser, const char **pp, const char *end)
{
const char *p = *pp;
// 先頭の空白をスキップ
while (p < end && (*p == ' ' || *p == '\t'))
p++;
const char *start = p;
// CRLFまで読む
while (p < end && *p != '\r')
p++;
if (p >= end)
return 0;
if (p + 1 >= end)
return 0;
if (*(p + 1) != '\n') {
parser->error_message = "Expected LF after CR";
return -1;
}
// ヘッダー値を保存
size_t len = p - start;
char *value = malloc(len + 1);
if (!value)
return -1;
ft_memcpy(value, start, len);
value[len] = '\0';
// ヘッダーをリストに追加
t_http_header *header = malloc(sizeof(t_http_header));
if (!header) {
free(value);
return -1;
}
header->name = parser->current_header_name;
header->value = value;
header->next = parser->request->headers;
parser->request->headers = header;
parser->current_header_name = NULL;
parser->state = PARSE_HEADER_NAME;
*pp = p + 2;
return 1;
}
// ボディ解析
static int parse_body(t_http_parser *parser, const char **pp, const char *end)
{
size_t available = end - *pp;
size_t needed = parser->request->content_length - parser->request->body_length;
size_t to_copy = available < needed ? available : needed;
if (!parser->request->body) {
parser->request->body = malloc(parser->request->content_length + 1);
if (!parser->request->body)
return -1;
}
ft_memcpy(parser->request->body + parser->request->body_length, *pp, to_copy);
parser->request->body_length += to_copy;
*pp += to_copy;
if (parser->request->body_length >= parser->request->content_length) {
parser->request->body[parser->request->body_length] = '\0';
parser->state = PARSE_COMPLETE;
}
return 1;
}
// メイン解析ループ
int http_parser_feed(t_http_parser *parser, const char *data, size_t len)
{
const char *p = data;
const char *end = data + len;
int result;
while (p < end && parser->state != PARSE_COMPLETE &&
parser->state != PARSE_ERROR) {
switch (parser->state) {
case PARSE_METHOD:
result = parse_method(parser, &p, end);
break;
case PARSE_URI:
result = parse_uri(parser, &p, end);
break;
case PARSE_VERSION:
result = parse_version(parser, &p, end);
break;
case PARSE_HEADER_NAME:
result = parse_header_name(parser, &p, end);
break;
case PARSE_HEADER_VALUE:
result = parse_header_value(parser, &p, end);
break;
case PARSE_BODY:
result = parse_body(parser, &p, end);
break;
default:
result = -1;
break;
}
if (result < 0) {
parser->state = PARSE_ERROR;
return -1;
}
if (result == 0)
break; // もっとデータが必要
}
return parser->state == PARSE_COMPLETE ? 1 : 0;
}
---
9.5 データ構造設計の原理
9.5.1 抽象データ型(ADT)の理論
ADTの概念
抽象データ型は、Barbara Liskovが1974年に提唱した概念。データの操作を、実装から分離した抽象として定義する。
___CODE_BLOCK_35___
キー・バリューストアのADT
___CODE_BLOCK_36___
9.5.2 ハッシュテーブルの理論と実装
ハッシュ関数の理論
良いハッシュ関数は以下の性質を持つ:
- 決定性:同じ入力には常に同じ出力
- 均一分布:出力が均等に分布
- 雪崩効果:入力の小さな変化が出力を大きく変える
- 効率性:計算が高速
___CODE_BLOCK_37___
衝突解決戦略
___CODE_BLOCK_38___
ハッシュテーブルの実装
___CODE_BLOCK_39___
---
9.6 イベント駆動アーキテクチャ
9.6.1 I/Oモデルの進化
同期 vs 非同期I/O
___CODE_BLOCK_40___
I/O多重化の進化
___CODE_BLOCK_41___
9.6.2 Reactorパターン
Douglas Schmidtが1995年に体系化したデザインパターン。
___CODE_BLOCK_42___mermaid graph TD subgraph Reactor Demuxer[Event Demuxer (epoll/kqueue)] Dispatcher[Event Dispatcher] Registry[Handler Registry] Demuxer -->|Events| Dispatcher Dispatcher -->|Callback| Registry Registry -->|fd1| AcceptHandler Registry -->|fd2| ReadHandler Registry -->|fd3| WriteHandler end subgraph Handlers AcceptHandler ReadHandler WriteHandler TimerHandler end Dispatcher --> Handlers ___CODE_BLOCK_43___
Reactorパターンの実装
___CODE_BLOCK_44___
エコーサーバーの実装例
___CODE_BLOCK_45___
---
9.7 Libftの統合パターン
9.7.1 実践的な統合例
コマンドラインツールへの統合
___CODE_BLOCK_46___
9.7.2 プロジェクト統合のMakefile
___CODE_BLOCK_47___
---
まとめ
この章では、ソフトウェアエンジニアリングの原理と、Libftを実世界のアプリケーションに統合する方法を学びました。
学んだ理論的基盤:
- モジュール性の理論
- ビルドシステムの原理
- メモリアロケータ設計
- プロトコル工学
- データ構造設計
- イベント駆動アーキテクチャ
これらの原理を理解することで、Libftの関数群を効果的に活用し、堅牢でスケーラブルなアプリケーションを構築できます。