第3章: ストレージクラスと状態管理の理論 - 静的変数の計算機科学的基盤

はじめに:なぜ変数は「消える」のか

プログラムにおいて、変数がいつ存在し、いつ消えるのかという問題は、計算機科学の根本的な課題です。この章では、記憶クラス(ストレージクラス)の理論的背景から始め、C言語の静的変数がなぜget_next_lineで必要なのかを深く理解します。

3.1 プログラムメモリの歴史

3.1.1 機械語時代:すべてが静的だった

1940年代〜1950年代初頭

最初期のコンピュータでは、すべてのデータは固定されたメモリアドレスに配置されていました:

ENIAC / EDVAC 時代(1940年代後半)
═══════════════════════════════════════════════════════

メモリモデル:
┌─────────────────────────────────────────────────────┐
│ アドレス 0000: 命令 1                                │
│ アドレス 0001: 命令 2                                │
│ ...                                                 │
│ アドレス 1000: データ A                              │
│ アドレス 1001: データ B                              │
│ ...                                                 │
└─────────────────────────────────────────────────────┘

特徴:
- すべての変数は絶対アドレスで参照
- 「変数」という概念はない(メモリ位置のみ)
- プログラムとデータの境界は不明確
- 再帰は不可能(スタックがない)

アセンブリ言語での表現:

; 1950年代風のアセンブリ
; 変数Aと変数Bを加算してCに格納
        LOAD    1000        ; アドレス1000(データA)を読み込み
        ADD     1001        ; アドレス1001(データB)を加算
        STORE   1002        ; アドレス1002(データC)に格納

; 問題:
; - アドレス1000, 1001, 1002 は固定
; - 別の場所にデータを置くとプログラムを書き換える必要
; - サブルーチンを再帰的に呼べない

3.1.2 サブルーチンと局所変数の発明

FORTRAN(1957)の登場

最初の高級言語FORTRANは、「変数」という抽象化を導入しましたが、すべての変数は静的でした:

C     FORTRAN I の例
      SUBROUTINE CALC
      INTEGER A, B, C
C     A, B, C は常に同じメモリに配置
C     サブルーチンが終了しても値は保持される
      A = A + 1
      RETURN
      END

C     問題:再帰ができない
      SUBROUTINE FACT(N, RESULT)
      INTEGER N, RESULT
      IF (N .LE. 1) THEN
          RESULT = 1
      ELSE
C         CALL FACT(N-1, TEMP) ← これはFORTRAN Iでは不可能
          ...
      END IF
      END

ALGOL 60(1960)の革命

ALGOL 60は、ブロック構造とスタックベースのメモリ管理を導入しました。これが現代の「ローカル変数」の起源です:

ALGOL 60 の革新
═══════════════════════════════════════════════════════

1. ブロック構造(Block Structure)
   begin
       integer a;  ← この 'a' はブロック内でのみ有効
       a := 1;
       begin
           integer a;  ← これは別の 'a'(内側のブロック)
           a := 2;
       end;
       ← ここでは最初の 'a' が復活(= 1)
   end

2. 動的なメモリ割り当て
   - 変数はブロック進入時に作成
   - ブロック脱出時に破棄
   - これにより再帰が可能に!

3. スタックの概念
   - Last In, First Out (LIFO)
   - 関数呼び出しのネストを自然に表現
   - 「活性化レコード(Activation Record)」の導入

3.1.3 活性化レコード(Activation Record)

Dijkstraが1960年代に形式化した概念で、関数呼び出しの状態を管理する仕組みです:

活性化レコードの構造
═══════════════════════════════════════════════════════

関数が呼び出されると、スタックに以下の情報が積まれる:

┌────────────────────────────────────────────────────┐
│              活性化レコード                         │
├────────────────────────────────────────────────────┤
│ 戻りアドレス(Return Address)                      │
│   → 呼び出し元の次の命令                           │
├────────────────────────────────────────────────────┤
│ 動的リンク(Dynamic Link)                          │
│   → 呼び出し元の活性化レコードへのポインタ          │
├────────────────────────────────────────────────────┤
│ 静的リンク(Static Link)                           │
│   → 語彙的に囲む環境へのポインタ(ネスト時)        │
├────────────────────────────────────────────────────┤
│ パラメータ                                          │
│   → 関数に渡された引数                             │
├────────────────────────────────────────────────────┤
│ ローカル変数                                        │
│   → 関数内で宣言された変数                         │
├────────────────────────────────────────────────────┤
│ 一時領域                                            │
│   → 式の計算に使用                                 │
└────────────────────────────────────────────────────┘

例:factorial(3) の呼び出し
─────────────────────────────

Stack (高位アドレス→低位アドレス)
┌────────────────────┐ ← Stack Top
│ factorial(1)       │
│ ├─ return addr     │
│ ├─ n = 1          │
│ └─ result = 1     │
├────────────────────┤
│ factorial(2)       │
│ ├─ return addr     │
│ ├─ n = 2          │
│ └─ waiting...     │
├────────────────────┤
│ factorial(3)       │
│ ├─ return addr     │
│ ├─ n = 3          │
│ └─ waiting...     │
├────────────────────┤
│ main()             │
└────────────────────┘ ← Stack Bottom

3.1.4 C言語の誕生とストレージクラス(1972年)

Dennis RitchieがUnix用に開発したC言語は、ALGOLのアイデアを実用的な形で実装しました:

/* C言語の4つのストレージクラス(1972年〜) */

/* 1. auto(自動変数)- デフォルト */
void function(void)
{
    int x;        /* auto int x; と同じ */
    auto int y;   /* 明示的な auto(現代ではほぼ使用されない) */
}

/* 2. static(静的変数) */
void function(void)
{
    static int count = 0;  /* 関数呼び出し間で保持 */
    count++;
}

/* 3. register(レジスタ変数)- 現代ではほぼ無意味 */
void function(void)
{
    register int i;  /* 高速アクセスのヒント */
    for (i = 0; i < 100; i++)
        /* ... */;
}

/* 4. extern(外部変数) */
extern int global_counter;  /* 他のファイルで定義された変数 */

C言語のストレージモデル:

C言語のメモリセグメント(Unix環境)
═══════════════════════════════════════════════════════

高位アドレス
┌─────────────────────────────────────────────────────┐
│                   Stack                              │
│     ← 自動変数(auto)、関数引数、戻りアドレス       │
│     ← 高位アドレスから低位へ成長                     │
├─────────────────────────────────────────────────────┤
│                    ↓                                 │
│                (空き領域)                          │
│                    ↑                                 │
├─────────────────────────────────────────────────────┤
│                   Heap                               │
│     ← malloc/free で管理                            │
│     ← 低位アドレスから高位へ成長                     │
├─────────────────────────────────────────────────────┤
│                   BSS                                │
│     ← 未初期化の static/global 変数                 │
│     ← プログラム起動時に0で初期化                    │
├─────────────────────────────────────────────────────┤
│                   Data                               │
│     ← 初期化済みの static/global 変数               │
│     ← コンパイル時に値が決定                         │
├─────────────────────────────────────────────────────┤
│                   Text                               │
│     ← プログラムコード(機械語命令)                 │
│     ← 読み取り専用                                   │
└─────────────────────────────────────────────────────┘
低位アドレス

3.2 記憶域期間(Storage Duration)の理論

3.2.1 記憶域期間の分類

C言語標準(C11/C17)では、オブジェクトの記憶域期間を4種類に分類しています:

記憶域期間の理論的分類
═══════════════════════════════════════════════════════

1. Static Storage Duration(静的記憶域期間)
─────────────────────────────────────────
   存在期間:プログラム開始 → プログラム終了
   初期化:プログラム起動前に一度だけ
   該当:
   - ファイルスコープの変数(グローバル変数)
   - static修飾された変数

   例:
   int global;             /* 静的記憶域期間 */
   static int file_var;    /* 静的記憶域期間 */
   void f(void) {
       static int local;   /* 静的記憶域期間 */
   }

2. Automatic Storage Duration(自動記憶域期間)
───────────────────────────────────────────────
   存在期間:ブロック進入 → ブロック脱出
   初期化:ブロックに入るたびに
   該当:
   - ブロックスコープで宣言された非static変数

   例:
   void f(void) {
       int local;          /* 自動記憶域期間 */
       for (int i = 0; ...) {
           int inner;      /* 自動記憶域期間 */
       }
   }

3. Thread Storage Duration(スレッド記憶域期間)
───────────────────────────────────────────────
   存在期間:スレッド開始 → スレッド終了
   初期化:スレッド開始時
   該当:
   - _Thread_local修飾された変数(C11以降)

   例:
   _Thread_local int thread_counter;

4. Allocated Storage Duration(割当記憶域期間)
─────────────────────────────────────────────
   存在期間:malloc → free
   初期化:プログラマの責任
   該当:
   - 動的メモリ割り当て

   例:
   int *p = malloc(sizeof(int));
   /* ... 使用 ... */
   free(p);

3.2.2 束縛時期(Binding Time)

変数とメモリアドレスの関連付け(束縛)がいつ行われるかという理論的概念:

束縛時期の分類
═══════════════════════════════════════════════════════

1. コンパイル時束縛(Compile-time Binding)
─────────────────────────────────────────
   時期:コンパイラがプログラムを処理するとき
   対象:静的変数のアドレス、定数

   例:
   static int x = 42;
   /* x のアドレスは実行ファイル内で決定 */
   /* ただし、位置独立コード(PIC)では再配置される */

2. リンク時束縛(Link-time Binding)
───────────────────────────────────
   時期:リンカがオブジェクトファイルを結合するとき
   対象:グローバル変数、extern変数

   例:
   /* file1.c */
   int shared_var = 100;

   /* file2.c */
   extern int shared_var;
   /* リンク時に shared_var のアドレスが解決 */

3. ロード時束縛(Load-time Binding)
───────────────────────────────────
   時期:プログラムがメモリにロードされるとき
   対象:動的リンクライブラリ、再配置

   例:
   /* 共有ライブラリの関数 */
   printf("Hello");
   /* printf のアドレスはロード時に決定 */

4. 実行時束縛(Run-time Binding)
────────────────────────────────
   時期:プログラム実行中
   対象:ローカル変数、動的メモリ

   例:
   void f(void) {
       int local;  /* f() が呼ばれるたびにアドレスが決定 */
       int *p = malloc(100);  /* malloc時にアドレスが決定 */
   }

3.2.3 静的変数の初期化の理論

静的変数の初期化は、厳密なルールに従います:

/* 初期化のルール */

/* 1. 定数式のみ許可 */
static int a = 42;              /* OK: 整数リテラル */
static int b = 10 + 20;         /* OK: 定数式 */
static int c = sizeof(int);     /* OK: sizeof は定数式 */
static int *p = NULL;           /* OK: NULL は定数 */

/* 2. 関数呼び出しは不可 */
int get_value(void) { return 42; }
static int d = get_value();     /* エラー! */

/* 3. 非定数変数は不可 */
int runtime_value = 42;
static int e = runtime_value;   /* エラー! */

/* 4. 複合リテラル(C99以降) */
static int arr[] = {1, 2, 3};   /* OK */
static struct { int x, y; } pt = {1, 2};  /* OK */

なぜこの制限があるのか?

静的変数の初期化制限の理論的根拠
═══════════════════════════════════════════════════════

理由1: 初期化タイミングの問題
─────────────────────────────
   静的変数は main() 実行前に初期化される
   → この時点で関数は呼べない
   → ランタイムの値も存在しない

   実行順序:
   1. プログラムがメモリにロード
   2. BSS セクションが0で初期化
   3. Data セクションが初期値で設定 ← ここで静的変数初期化
   4. 動的リンカが処理
   5. main() が呼ばれる

理由2: 決定性(Determinism)
─────────────────────────────
   プログラムは毎回同じ初期状態で開始すべき
   → 関数の戻り値は実行時に変わる可能性がある
   → 非決定的な初期化は避けるべき

理由3: 実行ファイルへの埋め込み
───────────────────────────────
   初期化された静的変数の値は、実行ファイルに含まれる
   → コンパイル時に値が確定している必要がある

   $ size a.out
      text    data     bss     dec     hex filename
      1234      56      16    1306     51a a.out
                ^^
                初期化された静的変数のサイズ

3.3 スコープと可視性の理論

3.3.1 レキシカルスコープ(Lexical Scoping)

C言語はレキシカルスコープ(静的スコープ)を採用しています:

レキシカルスコープ vs ダイナミックスコープ
═══════════════════════════════════════════════════════

レキシカルスコープ(C, Java, Python 等)
───────────────────────────────────────
   変数の参照は「プログラムテキスト」で決まる
   コンパイル時に変数の参照先が確定

   int x = 10;

   void f(void) {
       printf("%d", x);  /* 常にグローバルの x */
   }

   void g(void) {
       int x = 20;
       f();  /* 10 を出力(グローバルの x) */
   }

ダイナミックスコープ(Bash, 古いLisp 等)
─────────────────────────────────────────
   変数の参照は「呼び出し履歴」で決まる
   実行時に変数の参照先が決まる

   x=10

   f() {
       echo $x  /* 呼び出し元の x */
   }

   g() {
       local x=20
       f  # 20 を出力(g の x)
   }

3.3.2 名前隠蔽(Name Hiding)

内側のスコープの変数が外側の変数を「隠す」現象:

int x = 10;  /* グローバルスコープ */

void outer(void)
{
    int x = 20;  /* outer のローカルスコープ - グローバルを隠す */

    {
        int x = 30;  /* ブロックスコープ - outer の x を隠す */
        printf("%d\n", x);  /* 30 */
    }

    printf("%d\n", x);  /* 20 */
}

/* グローバルの x は影響を受けない */

シンボルテーブルによる名前解決:

コンパイラの名前解決プロセス
═══════════════════════════════════════════════════════

シンボルテーブルの構造(スタックベース):

識別子 'x' の参照を解決するとき:
1. 現在のスコープを検索
2. 見つからなければ外側のスコープを検索
3. 見つからなければさらに外側...
4. 最終的にグローバルスコープを検索

┌────────────────────────────────────────┐
│ Block Scope (innermost)                │
│ ┌──────────────────────────────────┐  │
│ │ x → int (address: stack+8)       │  │ ← 最初に検索
│ └──────────────────────────────────┘  │
├────────────────────────────────────────┤
│ Function Scope                         │
│ ┌──────────────────────────────────┐  │
│ │ x → int (address: stack+0)       │  │ ← 次に検索
│ └──────────────────────────────────┘  │
├────────────────────────────────────────┤
│ File Scope (Global)                    │
│ ┌──────────────────────────────────┐  │
│ │ x → int (address: 0x601020)      │  │ ← 最後に検索
│ └──────────────────────────────────┘  │
└────────────────────────────────────────┘

3.3.3 static とスコープ制限

static キーワードには、スコープを制限する効果もあります:

/* file1.c */
static int file_private = 42;  /* このファイル内でのみ可視 */

void use_private(void)
{
    printf("%d\n", file_private);  /* OK */
}

/* file2.c */
extern int file_private;  /* リンクエラー! */
/* 'file_private' への未定義参照 */

リンケージ(Linkage)の概念:

リンケージの分類
═══════════════════════════════════════════════════════

1. External Linkage(外部リンケージ)
───────────────────────────────────
   他のファイルから参照可能
   デフォルトでグローバル変数/関数

   /* file1.c */
   int global_var = 10;      /* 外部リンケージ */
   void global_func(void);   /* 外部リンケージ */

   /* file2.c */
   extern int global_var;    /* OK */
   extern void global_func(void);  /* OK */

2. Internal Linkage(内部リンケージ)
────────────────────────────────────
   同じファイル内でのみ参照可能
   static 修飾された変数/関数

   /* file1.c */
   static int private_var = 10;      /* 内部リンケージ */
   static void private_func(void);   /* 内部リンケージ */

   /* file2.c */
   extern int private_var;  /* リンクエラー! */

3. No Linkage(リンケージなし)
───────────────────────────────
   関数内のローカル変数

   void f(void) {
       int local;          /* リンケージなし */
       static int slocal;  /* 内部リンケージ(厳密には) */
   }

3.4 状態と純粋関数の理論

3.4.1 純粋関数(Pure Function)

関数型プログラミングの重要な概念:

純粋関数の定義
═══════════════════════════════════════════════════════

純粋関数とは:
1. 同じ引数に対して常に同じ結果を返す(参照透過性)
2. 副作用がない(外部状態を変更しない)

純粋関数の例:
────────────────
int add(int a, int b)
{
    return a + b;  /* 常に同じ入力 → 同じ出力 */
}

int square(int x)
{
    return x * x;
}

size_t ft_strlen(const char *s)
{
    size_t len = 0;
    while (s[len])
        len++;
    return len;
}

非純粋関数の例:
────────────────
int global_counter = 0;

int increment(void)
{
    return ++global_counter;  /* グローバル状態を変更 */
}

int get_random(void)
{
    return rand();  /* 呼び出しごとに異なる値 */
}

char *get_next_line(int fd)
{
    static char *buffer;  /* 内部状態を保持 */
    /* ... */
}

3.4.2 副作用(Side Effects)

副作用の分類
═══════════════════════════════════════════════════════

1. 状態変更(State Mutation)
─────────────────────────────
   グローバル変数の変更
   静的変数の変更
   引数として渡されたオブジェクトの変更

   void mutate(int *p) { *p = 42; }

2. I/O操作
──────────
   ファイル読み書き
   標準入出力
   ネットワーク通信

   printf("Hello");
   read(fd, buf, 100);

3. 例外/非局所ジャンプ
───────────────────
   setjmp/longjmp
   signal

get_next_line の副作用:
───────────────────────
char *get_next_line(int fd)
{
    static char *remainder;  /* 副作用1: 静的変数の変更 */
    char buffer[BUFFER_SIZE];
    read(fd, buffer, ...);   /* 副作用2: ファイル読み取り */
    /* ... */
}

→ get_next_line は意図的に非純粋関数
→ 状態を保持することが仕様の一部

3.4.3 なぜ get_next_line は静的変数を必要とするのか

get_next_line の仕様と状態
═══════════════════════════════════════════════════════

問題設定:
   ファイルから1行ずつ読み取る
   ただし、バッファサイズは固定

課題:
   read() は「行」を認識しない
   → 改行の途中でバッファが切れることがある
   → 読み過ぎた部分を保存する必要がある

例:
   ファイル: "Hello\nWorld\n"
   BUFFER_SIZE = 10

   1回目の read(): "Hello\nWorl" を読み込み
                    ^^^^^^^^ これを返したい
                            ^^^^ これを保存する必要

   2回目の get_next_line() 呼び出し:
   → 前回保存した "Worl" から処理を開始

解決策:状態の保持
─────────────────
   Option 1: 静的変数(C言語的解決)
   static char *remainder;

   Option 2: コンテキスト引数(関数型的解決)
   char *get_next_line(int fd, char **context);

   Option 3: オブジェクト/構造体(OOP的解決)
   typedef struct s_reader { ... } t_reader;
   char *reader_get_line(t_reader *reader);

42の課題では Option 1 が要求される
→ 関数シグネチャが固定されているため

3.5 メモリレイアウトの理論

3.5.1 プロセスメモリモデル

プロセスの仮想アドレス空間(64bit Linux)
═══════════════════════════════════════════════════════

0xFFFFFFFFFFFFFFFF ┌──────────────────────────────────┐
                   │          Kernel Space            │
                   │    (ユーザーからアクセス不可)    │
0x7FFFFFFFFFFF     ├──────────────────────────────────┤
                   │                                  │
                   │            Stack                 │
                   │     ↓ 下方向に成長               │
                   │                                  │
                   ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
                   │                                  │
                   │        (空き領域)               │
                   │                                  │
                   ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
                   │                                  │
                   │            Heap                  │
                   │     ↑ 上方向に成長               │
                   │                                  │
                   ├──────────────────────────────────┤
                   │            BSS                   │
                   │   (未初期化静的データ)          │
                   ├──────────────────────────────────┤
                   │            Data                  │
                   │   (初期化済み静的データ)        │
                   ├──────────────────────────────────┤
                   │            Text                  │
                   │   (プログラムコード)            │
0x0000000000400000 ├──────────────────────────────────┤
                   │        (予約領域)               │
0x0000000000000000 └──────────────────────────────────┘

3.5.2 各セグメントの詳細

Text セグメント:

/* Text セグメント - プログラムコード */
void function(void)     /* この関数のマシンコード */
{
    int x = 42;         /* この命令も Text に含まれる */
    printf("Hello\n");  /* 関数呼び出し命令も Text */
}

/* 特性:
 * - 読み取り専用(書き込み禁止)
 * - 実行可能
 * - 複数プロセスで共有可能(コード共有)
 */

Data セグメント:

/* Data セグメント - 初期化済みの静的データ */
int global_init = 42;           /* Data */
static int file_static = 100;   /* Data */
char *str = "Hello";            /* ポインタ自体は Data、文字列は rodata */

void function(void)
{
    static int local_init = 50; /* Data */
}

/* 特性:
 * - 読み書き可能
 * - 初期値が実行ファイルに含まれる
 * - プログラムロード時に値がコピーされる
 */

BSS セグメント:

/* BSS (Block Started by Symbol) - 未初期化の静的データ */
int global_uninit;              /* BSS */
static int file_static_uninit;  /* BSS */
static char buffer[1024];       /* BSS */

void function(void)
{
    static int local_uninit;    /* BSS */
}

/* 特性:
 * - 読み書き可能
 * - 実行ファイルにはサイズ情報のみ(データなし)
 * - プログラム起動時に0で初期化
 * - 実行ファイルサイズを小さく保てる
 */

BSS の名前の由来:

BSS = "Block Started by Symbol"

歴史(1950年代):
───────────────
IBMのアセンブラで使用されたディレクティブ
  BSS n  ; n バイトの未初期化領域を確保

例:
  SYMBOL1  BSS 100   ; SYMBOL1 から100バイト確保
  SYMBOL2  BSS 50    ; SYMBOL2 から50バイト確保

現代での意味:
   未初期化のグローバル/静的変数を格納するセグメント

3.5.3 get_next_line のメモリ配置

/* get_next_line のメモリレイアウト分析 */

char *get_next_line(int fd)
{
    /* remainder: BSS セグメント
     * - 静的ポインタ変数
     * - 初期値 0 (NULL)
     * - プログラム全体で1つだけ存在
     */
    static char *remainder = NULL;

    /* buffer: Stack セグメント
     * - 自動変数(配列)
     * - 関数呼び出しごとに作成/破棄
     * - BUFFER_SIZE + 1 バイト
     */
    char buffer[BUFFER_SIZE + 1];

    /* line: Stack セグメント
     * - 自動変数(ポインタ)
     * - 8 バイト(64bit)または 4 バイト(32bit)
     */
    char *line;

    /* malloc で確保した領域: Heap セグメント
     * - 動的メモリ
     * - free するまで存在
     * - remainder が指す先
     * - line が指す先(返却値)
     */
    line = malloc(100);
    remainder = malloc(BUFFER_SIZE);

    return line;  /* Heap 上のアドレスを返す */
}

図解:

メモリ配置の視覚化
═══════════════════════════════════════════════════════

[BSS セグメント]
┌──────────────────────────────────────────────────────┐
│ 変数名: remainder                                    │
│ サイズ: 8 bytes (64bit ポインタ)                     │
│ 値: 0x00007f8a12345000 (Heapを指す)                  │
│ アドレス: 0x601040                                   │
└──────────────────────────────────────────────────────┘

[Heap セグメント]
┌──────────────────────────────────────────────────────┐
│ アドレス: 0x00007f8a12345000                         │
│ サイズ: 可変(読み込んだデータ量による)              │
│ 内容: "Hello World\n" (前回の余り)                   │
│ 解放責任: プログラマ                                 │
└──────────────────────────────────────────────────────┘

[Stack セグメント] - get_next_line 実行中
┌──────────────────────────────────────────────────────┐
│ 戻りアドレス: 0x00000000004011a0                     │
├──────────────────────────────────────────────────────┤
│ 変数: fd = 3                                         │
├──────────────────────────────────────────────────────┤
│ 変数: buffer[BUFFER_SIZE + 1]                        │
│ サイズ: BUFFER_SIZE + 1 bytes                        │
│ 内容: "Next line\n\0..."                             │
├──────────────────────────────────────────────────────┤
│ 変数: line                                           │
│ サイズ: 8 bytes                                      │
│ 値: 0x00007f8a12346000 (Heapを指す)                  │
└──────────────────────────────────────────────────────┘

3.6 静的変数の実践的活用

3.6.1 get_next_line の基本パターン

/* 基本的な静的変数の使用パターン */

char *get_next_line(int fd)
{
    static char *remainder = NULL;  /* 状態を保持 */
    char buffer[BUFFER_SIZE + 1];
    char *line;
    ssize_t bytes_read;

    /* パラメータ検証 */
    if (fd < 0 || BUFFER_SIZE <= 0)
        return (NULL);

    /* 読み込みと処理 */
    while (!has_newline(remainder))
    {
        bytes_read = read(fd, buffer, BUFFER_SIZE);

        if (bytes_read <= 0)
            break;

        buffer[bytes_read] = '\0';
        remainder = append_to_remainder(remainder, buffer);
    }

    /* 行の抽出と残りの更新 */
    line = extract_line(&remainder);

    return (line);
}

3.6.2 複数ファイルディスクリプタ対応(ボーナス)

/* 複数fd対応:配列による状態管理 */

#ifndef OPEN_MAX
# define OPEN_MAX 1024
#endif

char *get_next_line(int fd)
{
    /* 各fdに対応する状態を配列で管理 */
    static char *remainders[OPEN_MAX];

    if (fd < 0 || fd >= OPEN_MAX || BUFFER_SIZE <= 0)
        return (NULL);

    /* fd に対応する remainder を使用 */
    return (process_fd(fd, &remainders[fd]));
}

/*
 * メモリレイアウト:
 *
 * [BSS セグメント]
 * remainders[0]    = NULL or Heap pointer (stdin)
 * remainders[1]    = NULL or Heap pointer (stdout)
 * remainders[2]    = NULL or Heap pointer (stderr)
 * remainders[3]    = NULL or Heap pointer (最初のファイル)
 * remainders[4]    = NULL or Heap pointer (2番目のファイル)
 * ...
 * remainders[1023] = NULL or Heap pointer
 *
 * 合計サイズ: 8 * 1024 = 8KB(64bit環境)
 */

3.6.3 状態のライフサイクル管理

/* 状態の作成・更新・破棄 */

static char *create_state(const char *initial)
{
    if (!initial || !*initial)
        return (NULL);
    return (ft_strdup(initial));
}

static char *update_state(char *state, const char *addition)
{
    char *new_state;

    if (!state)
        return (create_state(addition));

    new_state = ft_strjoin(state, addition);
    free(state);  /* 古い状態を解放 */

    return (new_state);
}

static void destroy_state(char **state)
{
    if (state && *state)
    {
        free(*state);
        *state = NULL;
    }
}

/* get_next_line での使用 */
char *get_next_line(int fd)
{
    static char *remainder = NULL;

    /* エラー時は状態を破棄 */
    if (fd < 0)
    {
        destroy_state(&remainder);
        return (NULL);
    }

    /* 読み込みと状態更新 */
    while (/* ... */)
    {
        bytes_read = read(fd, buffer, BUFFER_SIZE);

        if (bytes_read < 0)
        {
            destroy_state(&remainder);  /* エラー時のクリーンアップ */
            return (NULL);
        }

        if (bytes_read == 0)
            break;

        remainder = update_state(remainder, buffer);
    }

    /* ... */
}

3.7 メモリリークの理論と防止

3.7.1 メモリリークの定義

メモリリーク(Memory Leak)
═══════════════════════════════════════════════════════

定義:
   確保したメモリへの参照を失い、
   解放できなくなった状態

正式な定義(形式手法的):
   Let M = set of allocated memory blocks
   Let R = set of reachable memory blocks
   Leak = M - R (確保されているが到達不能なブロック)

例:
   void leak(void) {
       char *p = malloc(100);
       p = malloc(200);  /* 最初の100バイトへの参照を失う */
   }                     /* → 100バイトがリーク */

結果:
1. メモリ使用量が増加し続ける
2. 長時間実行でシステムが不安定に
3. 最悪の場合、メモリ枯渇でクラッシュ

3.7.2 get_next_line でのリークパターン

/* パターン1: 中間結果の解放忘れ */

/* 悪い例 */
char *bad_join(char *s1, const char *s2)
{
    return (ft_strjoin(s1, s2));  /* s1 が解放されない */
}

/* 良い例 */
char *good_join(char *s1, const char *s2)
{
    char *result = ft_strjoin(s1, s2);
    free(s1);  /* 古いメモリを解放 */
    return (result);
}


/* パターン2: エラーパス での解放忘れ */

/* 悪い例 */
char *bad_read(int fd, char **remainder)
{
    char *buffer = malloc(BUFFER_SIZE + 1);
    if (!buffer)
        return (NULL);  /* remainder が解放されない */

    if (read(fd, buffer, BUFFER_SIZE) < 0)
    {
        free(buffer);
        return (NULL);  /* remainder が解放されない */
    }

    /* ... */
}

/* 良い例 */
char *good_read(int fd, char **remainder)
{
    char *buffer = malloc(BUFFER_SIZE + 1);
    if (!buffer)
    {
        free(*remainder);
        *remainder = NULL;
        return (NULL);
    }

    if (read(fd, buffer, BUFFER_SIZE) < 0)
    {
        free(buffer);
        free(*remainder);
        *remainder = NULL;
        return (NULL);
    }

    /* ... */
}


/* パターン3: 静的変数のクリーンアップ忘れ */

/* 問題のあるコード */
int main(void)
{
    int fd = open("file.txt", O_RDONLY);
    char *line = get_next_line(fd);

    /* 1行だけ読んで終了 */
    free(line);
    close(fd);
    return (0);
    /* remainder に残ったデータがリーク */
}

/* 解決策1: 最後まで読む */
int main(void)
{
    int fd = open("file.txt", O_RDONLY);
    char *line;

    while ((line = get_next_line(fd)) != NULL)
        free(line);

    close(fd);
    return (0);
}

/* 解決策2: クリーンアップ用の呼び出し */
int main(void)
{
    int fd = open("file.txt", O_RDONLY);
    char *line = get_next_line(fd);

    free(line);
    get_next_line(-1);  /* fd < 0 でクリーンアップ */
    close(fd);
    return (0);
}

3.7.3 メモリリーク検出

# Valgrind によるリーク検出

# コンパイル(デバッグ情報付き)
gcc -g -D BUFFER_SIZE=42 get_next_line.c main.c -o gnl

# リーク検出
valgrind --leak-check=full --show-leak-kinds=all ./gnl

# 出力例(リークあり)
==12345== LEAK SUMMARY:
==12345==    definitely lost: 1,024 bytes in 1 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 0 bytes in 0 blocks
==12345==    still reachable: 100 bytes in 1 blocks
==12345==         suppressed: 0 bytes in 0 blocks

# "definitely lost": 完全にリーク
# "still reachable": まだ到達可能(静的変数経由)

# 出力例(リークなし)
==12345== LEAK SUMMARY:
==12345==    definitely lost: 0 bytes in 0 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 0 bytes in 0 blocks
==12345==    still reachable: 0 bytes in 0 blocks
==12345==         suppressed: 0 bytes in 0 blocks
==12345== All heap blocks were freed -- no leaks are possible

3.8 スレッド安全性の理論

3.8.1 競合状態(Race Condition)

競合状態の理論
═══════════════════════════════════════════════════════

定義:
   複数の実行スレッドが共有リソースに同時アクセスし、
   実行順序によって結果が変わる状態

例:静的変数への同時アクセス

Thread A                    Thread B
─────────────────────────────────────────────────
1. remainder = "Hello"      |
2. read buffer              | 1. remainder = "World"
3. append to remainder      | 2. read buffer
4.    ↓                     | 3. append to remainder
   結果: "HelloWorld..."    |    結果: "World..."
   または予測不能な結果

3.8.2 スレッドローカルストレージ(TLS)

/* C11 以降 */
_Thread_local static char *remainder = NULL;

/* GCC/Clang 拡張 */
__thread static char *remainder = NULL;

/*
 * TLSの動作:
 *
 * Thread A: remainder_A → "Hello..."
 * Thread B: remainder_B → "World..."
 *
 * 各スレッドが独自のコピーを持つ
 * → 競合状態が発生しない
 */

3.8.3 42の評価基準

42 get_next_line の評価基準
═══════════════════════════════════════════════════════

要求されていないこと:
- スレッド安全性は要求されていない
- シングルスレッド環境での動作のみ保証すればよい

ボーナス要件:
- 複数fd対応は必要
- しかし、これは「同時」アクセスではない

/* 期待される動作 */
fd1 = open("file1.txt", O_RDONLY);
fd2 = open("file2.txt", O_RDONLY);

/* 順次アクセス(同時ではない) */
line1 = get_next_line(fd1);  /* OK */
line2 = get_next_line(fd2);  /* OK */
line1 = get_next_line(fd1);  /* OK */

/* これで十分 */

まとめ

この章では、静的変数の背後にある計算機科学の理論を学びました:

  • プログラムメモリの歴史
- 機械語時代からALGOL、C言語への進化 - 活性化レコードとスタックの概念

  • 記憶域期間の理論
- Static, Automatic, Thread, Allocated - 束縛時期(コンパイル時、リンク時、実行時)

  • スコープと可視性
- レキシカルスコープ - リンケージ(外部、内部、なし)

  • 状態と純粋関数
- 純粋関数 vs 副作用 - get_next_lineが静的変数を必要とする理由

  • メモリレイアウト
- Text, Data, BSS, Heap, Stack - 変数の配置場所

  • メモリリーク防止
- リークパターンの認識 - 検出と修正

次章では、バッファリングの理論とBUFFER_SIZEの意味について学びます。