第1章:コンピュータとC言語の本質

1.1 コンピュータの誕生と基本原理

1.1.1 計算機械の歴史

現代のコンピュータを理解するためには、その歴史的背景を知ることが重要です。コンピュータは突然現れたものではなく、人類が長年にわたって追求してきた「自動計算」という夢の結晶です。

機械式計算機の時代

1642年、フランスの数学者ブレーズ・パスカルは、歯車を使った計算機「パスカリーヌ」を発明しました。これは加減算を自動化した最初期の機械式計算機でした。税務官だった父の仕事を手伝うために作られたこの機械は、繰り上がりを歯車の回転で表現するという革新的なアイデアを持っていました。

パスカリーヌの動作原理:

  歯車A (一の位)    歯車B (十の位)
     ┌───┐            ┌───┐
     │ 9 │  ──────→   │ 0 │
     │→0 │  繰り上がり │→1 │
     └───┘            └───┘

一の位が9から0に変わるとき、
十の位の歯車が1つ回転する

1694年には、ゴットフリート・ライプニッツが乗除算も可能な「ライプニッツの車輪」を開発しました。彼はまた、二進法の重要性にも気づいていました。「0と1だけで全ての数を表現できる」というこの洞察は、約250年後のコンピュータ時代に花開くことになります。

チャールズ・バベッジの夢

19世紀、イギリスの数学者チャールズ・バベッジは「解析機関」を構想しました。これは、入力、記憶、演算、制御、出力という現代のコンピュータと同じ構成要素を持つ、世界初の汎用計算機の設計でした。

バベッジの解析機関の構成(1837年):

┌─────────────┐     ┌─────────────┐
│  ストア    │←───→│    ミル     │
│ (記憶装置)  │     │ (演算装置)   │
└─────────────┘     └─────────────┘
       ↑                   ↑
       │                   │
┌──────┴────────────────────┴──────┐
│         パンチカード入力          │
│         (プログラム)              │
└──────────────────────────────────┘

現代のコンピュータとの対応:
- **ストア** → RAM(メインメモリ)
- **ミル** → CPU(中央処理装置)
- **パンチカード** → プログラム

バベッジの解析機関は、当時の技術では製造できませんでしたが、その設計思想は驚くほど現代的でした。条件分岐、ループ、サブルーチンといった概念が既に含まれていたのです。

エイダ・ラブレスは、バベッジの解析機関のためのプログラム(ベルヌーイ数を計算するアルゴリズム)を書いた人物として知られています。彼女は単に計算手順を書いただけでなく、「機械は創造的思考はできないが、人間が指示したことは何でも実行できる」という洞察を残しました。これは現代のAI論議の原点とも言えます。

### 1.1.2 フォン・ノイマン・アーキテクチャ

**ENIAC から EDVAC へ**

1945年、世界初の電子式汎用コンピュータENIAC(Electronic Numerical Integrator and Computer)が完成しました。ENIACは約18,000本の真空管を使用し、弾道計算などの複雑な計算を実行できました。しかし、プログラムの変更には配線の物理的な組み換えが必要で、数日から数週間かかることもありました。

ENIAC のプログラミング:

┌────────────────────────────────────┐ │ 配線パネル │ │ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │ │ │○│─│○│─│○│─│○│─│○│ │ │ └──┘ └──┘ └──┘ └──┘ └──┘ │ │ ↓ ↓ ↓ ↓ ↓ │ │ ╔══════════════════════════╗ │ │ ║ 演算ユニット ║ │ │ ╚══════════════════════════╝ │ └────────────────────────────────────┘

問題: プログラムの変更 = 物理的な配線変更 → 非常に時間がかかる、エラーが起きやすい

この問題を解決するために、数学者ジョン・フォン・ノイマンを中心としたチームは、革命的なアイデアを提案しました。それが「プログラム内蔵方式」です。プログラム(命令)もデータと同様にメモリに格納し、順次読み出して実行するというこのアイデアは、EDVAC(Electronic Discrete Variable Automatic Computer)の設計報告書として1945年に発表されました。

フォン・ノイマン・アーキテクチャの構成

フォン・ノイマン・アーキテクチャ:

┌─────────────────────────────────────────────────┐
│                    CPU                          │
│  ┌───────────────┐    ┌───────────────────┐    │
│  │  制御装置      │    │    ALU            │    │
│  │ (Control Unit) │    │ (算術論理演算装置) │    │
│  │               │    │                   │    │
│  │ ・命令の解読   │    │ ・加減乗除        │    │
│  │ ・実行順序制御 │    │ ・論理演算        │    │
│  │ ・各部の協調   │    │ ・比較演算        │    │
│  └───────┬───────┘    └─────────┬─────────┘    │
│          │                      │              │
│  ┌───────┴──────────────────────┴───────┐      │
│  │           レジスタ群                 │      │
│  │  PC(プログラムカウンタ) IR(命令レジスタ) │      │
│  │  汎用レジスタ (R0, R1, R2, ...)     │      │
│  └───────────────────────────────────────┘      │
└─────────────────────┬───────────────────────────┘
                      │ バス(データ・アドレス・制御)
                      ↓
┌─────────────────────────────────────────────────┐
│              メインメモリ (RAM)                 │
│                                                 │
│  アドレス    内容                               │
│  0x0000  ┌─────────────┐                       │
│          │ プログラム   │ ← 命令もデータも     │
│  0x1000  ├─────────────┤   同じメモリに格納    │
│          │ データ      │                       │
│  0x2000  ├─────────────┤                       │
│          │ スタック    │                       │
│          └─────────────┘                       │
└─────────────────────────────────────────────────┘
                      │
                      ↓
┌─────────────────────────────────────────────────┐
│              入出力装置                         │
│  キーボード、ディスプレイ、ストレージ など       │
└─────────────────────────────────────────────────┘

フォン・ノイマン・アーキテクチャの核心原理:

  • プログラム内蔵方式: 命令とデータを同じメモリに格納
  • 逐次実行: 命令を一つずつ順番に実行
  • 二進法: 全ての情報を0と1で表現
  • アドレス指定: メモリの各位置に番号(アドレス)を付与
  • この設計は1945年に発表されて以来、約80年間、コンピュータの基本設計として使われ続けています。あなたが今使っているコンピュータ、スマートフォン、42のサーバーも、全てこのアーキテクチャの延長線上にあります。

    1.1.3 命令実行サイクル

    CPUがプログラムを実行する過程を理解することは、C言語の動作を理解する上で重要です。

    命令実行サイクル(フェッチ-デコード-実行):
    
    1. フェッチ (Fetch)
       ┌─────────────────────────────────────────┐
       │ PC(プログラムカウンタ)が指すアドレスから │
       │ 命令を読み込む                          │
       │                                         │
       │   PC: 0x1000 ──→ メモリ[0x1000]の命令   │
       │                  を IR(命令レジスタ)へ   │
       └─────────────────────────────────────────┘
                          ↓
    2. デコード (Decode)
       ┌─────────────────────────────────────────┐
       │ 命令を解読し、何をすべきか判断           │
       │                                         │
       │   IR: "ADD R1, R2, R3"                  │
       │   → R2とR3を足してR1に格納せよ          │
       └─────────────────────────────────────────┘
                          ↓
    3. 実行 (Execute)
       ┌─────────────────────────────────────────┐
       │ 実際の演算を行う                        │
       │                                         │
       │   ALU: R2(5) + R3(3) = 8               │
       │   結果をR1に格納                        │
       └─────────────────────────────────────────┘
                          ↓
    4. PCの更新
       ┌─────────────────────────────────────────┐
       │ PC = PC + 命令長                        │
       │ (次の命令のアドレスを指す)             │
       │                                         │
       │   PC: 0x1000 → 0x1004                  │
       └─────────────────────────────────────────┘
                          ↓
            最初に戻り、次の命令をフェッチ
    

    このサイクルは、コンピュータの電源が入っている限り、1秒間に数十億回(GHz単位)繰り返されます。現代のCPUは3GHz程度で動作するため、1秒間に約30億回の命令実行サイクルを回します。

    1.2 二進法とデータ表現

    1.2.1 なぜ二進法なのか

    コンピュータが二進法(0と1)を使う理由は、物理的な実装の容易さにあります。

    電子回路での表現:
    
    十進法を使う場合(仮想):
      0V = 0, 1V = 1, 2V = 2, ... 9V = 9
      → 電圧を正確に10段階で区別する必要
      → ノイズに弱い、製造が困難
    
    二進法を使う場合(現実):
      低電圧(0V付近) = 0, 高電圧(3.3V/5V付近) = 1
      → 電圧を2段階で区別するだけ
      → ノイズに強い、製造が容易
    
        電圧
        ↑
     5V │         ───────────── 論理 "1"
        │        ╱             ╲
     2.5│───────╱───────────────╲─── マージン領域
        │      ╱                 ╲
     0V │─────────────────────────── 論理 "0"
        └────────────────────────────→ 時間
    
    ※ ノイズがあっても、しきい値を超えなければ正しく判定
    

    ビットとバイト

  • ビット (bit): 0または1を表す最小単位。binary digitの略
  • バイト (byte): 8ビットの集まり。文字1つを表現するための歴史的な単位

1バイト = 8ビット の表現能力:

  ビット列          十進数
  00000000    =    0
  00000001    =    1
  00000010    =    2
  00000011    =    3
     ...           ...
  01111111    =    127
  10000000    =    128
     ...           ...
  11111110    =    254
  11111111    =    255

1バイトで表現できる範囲: 0 ~ 255 (2^8 = 256通り)

1.2.2 整数の二進表現

符号なし整数 (unsigned)

unsigned char (1バイト) の例:

  十進数 42 を二進数に変換:

  42 ÷ 2 = 21 余り 0  ↑
  21 ÷ 2 = 10 余り 1  │ 下から
  10 ÷ 2 = 5  余り 0  │ 読む
   5 ÷ 2 = 2  余り 1  │
   2 ÷ 2 = 1  余り 0  │
   1 ÷ 2 = 0  余り 1  │

  結果: 42(10) = 00101010(2)

  メモリ上の表現:
  ┌───┬───┬───┬───┬───┬───┬───┬───┐
  │ 0 │ 0 │ 1 │ 0 │ 1 │ 0 │ 1 │ 0 │
  └───┴───┴───┴───┴───┴───┴───┴───┘
   MSB                         LSB
  (最上位ビット)           (最下位ビット)
   2^7  2^6  2^5  2^4  2^3  2^2  2^1  2^0
   128   64   32   16    8    4    2    1

  計算: 0 + 0 + 32 + 0 + 8 + 0 + 2 + 0 = 42 ✓

符号付き整数と2の補数

負の数を表現するために、コンピュータは「2の補数」という方式を使います。

2の補数表現 (signed char = 1バイトの場合):

正の数: 最上位ビットが0
  +42 = 00101010

負の数: 最上位ビットが1
  -42 の求め方:

  Step 1: +42 の二進表現
          00101010

  Step 2: 全ビットを反転(1の補数)
          11010101

  Step 3: 1を加える(2の補数)
          11010101
        +        1
        ──────────
          11010110  ← これが -42

検算: -42 + 42 = ?
      11010110  (-42)
    + 00101010  (+42)
    ──────────
    1 00000000  ← 桁あふれを無視すると 0 ✓

2の補数の利点:
- 加算回路だけで減算も実現可能
- 0の表現が一意(+0と-0が別々にならない)

1.2.3 浮動小数点数

実数を表現するために、IEEE 754規格の浮動小数点表現が使われます。

IEEE 754 単精度浮動小数点数(float = 4バイト = 32ビット):

┌───┬─────────────┬───────────────────────────┐
│ S │  指数部 (8) │        仮数部 (23)        │
└───┴─────────────┴───────────────────────────┘
  1       8                    23           ビット

S: 符号ビット (0 = 正, 1 = 負)
指数部: 2の累乗を表す(バイアス127)
仮数部: 有効数字を表す(暗黙の1を含む)

例: -6.625 を表現

Step 1: 二進数に変換
  6.625(10) = 110.101(2)
  (6 = 4+2 = 110, 0.625 = 0.5+0.125 = 0.101)

Step 2: 正規化(1.xxx × 2^n の形に)
  110.101 = 1.10101 × 2^2

Step 3: 各フィールドを決定
  符号: 1 (負の数)
  指数: 2 + 127(バイアス) = 129 = 10000001
  仮数: 10101000000000000000000 (暗黙の1を除く)

結果:
┌───┬──────────┬───────────────────────────┐
│ 1 │ 10000001 │ 10101000000000000000000   │
└───┴──────────┴───────────────────────────┘

16進数: 0xC0D40000

浮動小数点の落とし穴

// この比較は危険
float a = 0.1;
float b = 0.2;
float c = 0.3;

if (a + b == c) {  // false になることがある!
    // ...
}

// なぜか?
// 0.1 は二進数では循環小数になる
// 0.1(10) = 0.0001100110011...(2)
// 有限ビットでは正確に表現できない

// 正しい比較方法
#define EPSILON 1e-6
if (fabs((a + b) - c) < EPSILON) {
    // ...
}

1.3 C言語の誕生と設計哲学

1.3.1 UNIXとC言語の歴史

C言語は1972年、AT&Tベル研究所でデニス・リッチーによって開発されました。その背景には、UNIXオペレーティングシステムの開発があります。

C言語の系譜:

1964  BCPL (Martin Richards)
       │    - シンプルなシステムプログラミング言語
       │    - 唯一のデータ型は「マシンワード」
       ↓
1969  B言語 (Ken Thompson)
       │    - BCPLをPDP-7用に簡略化
       │    - UNIXの最初期の開発に使用
       │    - 型の概念がない
       ↓
1972  C言語 (Dennis Ritchie)
       │    - B言語に型システムを追加
       │    - 構造体、ポインタ、豊富な演算子
       │    - PDP-11の機能を活かした設計
       ↓
1973  UNIXがCで書き直される
       │    - 移植性の飛躍的向上
       │    - OS自体がCで書ける証明
       ↓
1978  K&R "The C Programming Language"
       │    - 事実上の言語標準
       │    - プログラミング教科書の古典
       ↓
1989  ANSI C (C89)
       │    - 最初の公式標準
       │    - 関数プロトタイプの導入
       ↓
1999  C99
       │    - インライン関数、可変長配列
       │    - // コメント、long long型
       ↓
2011  C11
       │    - マルチスレッド、アトミック操作
       │    - 静的アサーション
       ↓
2023  C23 (最新)
            - さらなる改良と現代化

1.3.2 C言語の設計哲学

デニス・リッチーはC言語を設計する際、いくつかの重要な原則に従いました。これらの原則を理解することは、C言語を効果的に使う上で不可欠です。

1. プログラマを信頼する (Trust the Programmer)

// C言語はプログラマの意図を尊重し、過度な制限を設けない

// 配列の境界チェックは行わない
int arr[10];
arr[100] = 42;  // コンパイルは通る(実行時に問題が起きる)

// ポインタの型変換も自由
void *ptr = malloc(100);
int *iptr = (int *)ptr;      // OK
char *cptr = (char *)ptr;    // OK
struct X *xptr = (struct X *)ptr;  // OK

// この自由度が、C言語のパワーと危険性の両方の源

2. 小さく、シンプルに (Small is Beautiful)

// C言語のキーワードはわずか32個(C89)
auto     break    case     char     const    continue
default  do       double   else     enum     extern
float    for      goto     if       int      long
register return   short    signed   sizeof   static
struct   switch   typedef  union    unsigned void
volatile while

// 比較: Java は約50個、C++ は約90個のキーワード

// 複雑な機能はライブラリで提供
#include <stdio.h>   // 入出力
#include <stdlib.h>  // メモリ管理、ユーティリティ
#include <string.h>  // 文字列操作
#include <math.h>    // 数学関数

3. 効率性を最優先 (Efficiency Matters)

// C言語はハードウェアに近い抽象化を提供

// ポインタ = メモリアドレスの直接操作
int x = 42;
int *p = &x;        // p は x のメモリアドレスを保持
*p = 100;           // メモリを直接書き換え

// ビット演算 = CPUの演算を直接利用
unsigned int flags = 0;
flags |= (1 << 3);  // 第3ビットをセット
flags &= ~(1 << 3); // 第3ビットをクリア

// 構造体 = メモリレイアウトの明示的制御
struct Packet {
    uint8_t  version;    // 1バイト目
    uint8_t  type;       // 2バイト目
    uint16_t length;     // 3-4バイト目
    uint32_t checksum;   // 5-8バイト目
};
// ネットワークパケットやファイルフォーマットの直接操作が可能

4. 移植性 (Portability)

// C言語のコードは、異なるプラットフォームで動作可能

// 実装依存の部分は標準で定義
#include <limits.h>
printf("int は %d ビット\n", sizeof(int) * CHAR_BIT);
printf("int の最大値: %d\n", INT_MAX);

// 条件付きコンパイルでプラットフォーム差を吸収
#ifdef _WIN32
    #include <windows.h>
    #define SLEEP(ms) Sleep(ms)
#else
    #include <unistd.h>
    #define SLEEP(ms) usleep((ms) * 1000)
#endif

1.3.3 C言語とアセンブリの関係

C言語は「高級アセンブラ」と呼ばれることがあります。その理由を見てみましょう。

C言語のコードがどうマシン語になるか:

【Cコード】
int add(int a, int b) {
    return a + b;
}

【x86-64 アセンブリ(最適化なし)】
add:
    push    rbp              # スタックフレーム設定
    mov     rbp, rsp
    mov     DWORD PTR [rbp-4], edi   # 引数aをスタックに
    mov     DWORD PTR [rbp-8], esi   # 引数bをスタックに
    mov     edx, DWORD PTR [rbp-4]   # aをレジスタに
    mov     eax, DWORD PTR [rbp-8]   # bをレジスタに
    add     eax, edx                  # 加算
    pop     rbp              # スタックフレーム復元
    ret                      # 戻り値はeaxに

【x86-64 アセンブリ(最適化あり: -O2)】
add:
    lea     eax, [rdi+rsi]   # たった1命令!
    ret

// -O2 オプションで、コンパイラは最適なコードを生成
// 人間が書くより効率的になることも多い

1.4 コンパイルとリンクの仕組み

1.4.1 ソースコードから実行ファイルへ

C言語のソースコードがどのように実行可能なプログラムになるかを理解することは、デバッグや最適化に不可欠です。

コンパイルの4段階:

┌──────────────┐
│  hello.c     │  ソースファイル
│  #include... │
│  int main... │
└──────┬───────┘
       │ 1. プリプロセス (cpp)
       ↓
┌──────────────┐
│  hello.i     │  プリプロセス済みソース
│  (マクロ展開  │  #includeの展開、マクロ置換
│   #include   │  条件付きコンパイルの処理
│   の展開)    │
└──────┬───────┘
       │ 2. コンパイル (cc1)
       ↓
┌──────────────┐
│  hello.s     │  アセンブリコード
│  .text       │  人間が読める形式
│  main:       │  のマシン語表現
│    push %rbp │
└──────┬───────┘
       │ 3. アセンブル (as)
       ↓
┌──────────────┐
│  hello.o     │  オブジェクトファイル
│  (バイナリ)   │  マシン語だが、まだ
│              │  アドレス未解決
└──────┬───────┘
       │ 4. リンク (ld)
       │ ← ライブラリ (libc.a など)
       ↓
┌──────────────┐
│  hello       │  実行ファイル
│  (実行可能   │  全てのアドレスが
│   バイナリ)  │  解決済み
└──────────────┘

各段階を個別に実行する方法:

# 1. プリプロセスのみ(結果を見る)
gcc -E hello.c -o hello.i

# 2. コンパイルまで(アセンブリを見る)
gcc -S hello.c -o hello.s

# 3. アセンブルまで(オブジェクトファイル生成)
gcc -c hello.c -o hello.o

# 4. リンク(実行ファイル生成)
gcc hello.o -o hello

# 通常は一括で実行
gcc hello.c -o hello

1.4.2 プリプロセッサの動作

// 元のソースコード (example.c)
#include <stdio.h>
#define MAX_SIZE 100
#define SQUARE(x) ((x) * (x))

#ifdef DEBUG
    #define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
    #define LOG(msg)
#endif

int main() {
    int arr[MAX_SIZE];
    int result = SQUARE(5);
    LOG("計算完了");
    return 0;
}

// プリプロセス後 (gcc -E example.c の出力を簡略化)

// <stdio.h> の内容が展開される(数千行)
extern int printf(const char *format, ...);
// ... 他の宣言 ...

int main() {
    int arr[100];                    // MAX_SIZE が 100 に置換
    int result = ((5) * (5));        // SQUARE(5) が展開
                                     // LOG は空に展開(DEBUGが未定義の場合)
    return 0;
}

1.4.3 シンボル解決とリンク

リンカの役割:

hello.o の中身(シンボルテーブル):

| シンボル名 | 種類 | アドレス | 備考 | |:-----------|:-----|:---------|:-----| | main | 定義済み | 0x0000 | | | printf | 未定義 | ??? | 外部参照 |

libc.a の中身(一部):

| シンボル名 | 種類 | アドレス | |:-----------|:-----|:---------| | printf | 定義済み | 0x???? | | malloc | 定義済み | 0x???? | | ... | | |

リンク後:

hello.oprintflibc.a の printf のアドレスで置換。全ての未定義シンボルが解決されて、実行可能になる。

1.5 Libftプロジェクトへの導入

1.5.1 なぜ標準ライブラリを再実装するのか

ここまでコンピュータとC言語の基礎を学んできました。では、なぜ42のカリキュラムでは標準ライブラリの関数を自分で再実装するのでしょうか?

教育的価値

標準ライブラリは「ブラックボックス」:

  あなたのコード         標準ライブラリ
┌─────────────────┐    ┌─────────────────┐
│ char *s = "Hi"; │    │                 │
│ int len;        │────→│    strlen()     │────→ 戻り値: 2
│ len = strlen(s);│    │   (中身は?)    │
└─────────────────┘    └─────────────────┘

自分で実装することで:
- メモリがどう操作されるか理解
- ポインタが何を指しているか把握
- なぜその計算量になるか説明可能
- エッジケースを自分で考える経験

実務での応用

Libftスキルの活用場面:

1. 組み込みシステム
   ┌──────────────────────────────────────┐
   │ マイコン(Arduino、ESP32など)        │
   │ - メモリ: 数KB〜数百KB               │
   │ - 標準ライブラリが使えない/制限される │
   │ - 自分で基本関数を実装する必要       │
   └──────────────────────────────────────┘

2. カーネル/ドライバ開発
   ┌──────────────────────────────────────┐
   │ OS の内部                            │
   │ - ユーザー空間のライブラリは使えない  │
   │ - カーネル専用の実装が必要           │
   │ - Linux の lib/string.c など         │
   └──────────────────────────────────────┘

3. パフォーマンスクリティカルな場面
   ┌──────────────────────────────────────┐
   │ ゲーム、HFT、リアルタイムシステム     │
   │ - 汎用実装では遅すぎることがある      │
   │ - 特定用途に最適化した実装が必要     │
   └──────────────────────────────────────┘

1.5.2 Libftで実装する関数群

Libftで実装する関数は、大きく4つのカテゴリに分類されます。

Part 1: 標準ライブラリ関数の再実装

| カテゴリ | 関数 | |:---------|:-----| | 文字処理関数(第2章で解説) | isalpha, isdigit, isalnum, isascii, isprint, toupper, tolower | | 文字列関数(第4章で解説) | strlen, strchr, strrchr, strncmp, strnstr, strlcpy, strlcat | | メモリ関数(第3章で解説) | memset, bzero, memcpy, memmove, memchr, memcmp | | 変換関数(第4章で解説) | atoi |

Part 2: 追加関数

| カテゴリ | 関数 | |:---------|:-----| | メモリ管理(第5章で解説) | ft_calloc, ft_strdup | | 文字列操作(第5章で解説) | ft_substr, ft_strjoin, ft_strtrim, ft_split, ft_itoa, ft_strmapi, ft_striteri | | 出力関数(第5章で解説) | ft_putchar_fd, ft_putstr_fd, ft_putendl_fd, ft_putnbr_fd |

Bonus: 連結リスト

| カテゴリ | 関数 | |:---------|:-----| | リスト操作(第6章で解説) | ft_lstnew, ft_lstadd_front, ft_lstsize, ft_lstlast, ft_lstadd_back, ft_lstdelone, ft_lstclear, ft_lstiter, ft_lstmap |

1.5.3 本ガイドの構成

本ガイドでは、コンピュータサイエンスの基礎知識を先に深く解説し、その後でLibftの関数実装に繋げるという構成を採用しています。

各章の構成パターン:

┌─────────────────────────────────────────────────────┐
│                    第N章                            │
├─────────────────────────────────────────────────────┤
│ 1. CS基礎知識                                       │
│    - 歴史的背景                                     │
│    - 理論的基盤                                     │
│    - 実務での重要性                                 │
├─────────────────────────────────────────────────────┤
│ 2. 深掘り                                          │
│    - 詳細な仕組み                                   │
│    - 関連する概念                                   │
│    - よくある誤解                                   │
├─────────────────────────────────────────────────────┤
│ 3. Libftへの応用                                    │
│    - 関数の仕様                                     │
│    - 実装のポイント                                 │
│    - エッジケース                                   │
├─────────────────────────────────────────────────────┤
│ 4. 発展的話題                                       │
│    - 最適化                                        │
│    - 実世界での使用例                               │
│    - 次の学習への橋渡し                            │
└─────────────────────────────────────────────────────┘

各章の概要:

| 章 | タイトル | CS基礎トピック | Libft関数 | |:--:|:---------|:--------------|:----------| | 1 | コンピュータとC言語の本質 | アーキテクチャ、二進法 | 概要 | | 2 | 文字エンコーディングの世界 | ASCII、Unicode | isalpha等 | | 3 | メモリの深淵 | メモリ階層、ポインタ | memset等 | | 4 | 文字列という抽象化 | ヌル終端、バッファ | strlen等 | | 5 | 動的メモリ管理 | malloc内部、フラグメンテーション | calloc等 | | 6 | データ構造入門 | 計算量、抽象データ型 | 連結リスト | | 7 | アルゴリズムの計算量 | ビッグO、時間/空間計算量 | 分析 | | 8 | テストとデバッグ | テスト理論、デバッガ | テスト戦略 | | 9 | 最適化の科学 | プロファイリング、キャッシュ | 最適化 | | 10 | システムプログラミングへ | OS、システムコール | 次へ |

1.6 開発環境のセットアップ

1.6.1 必要なツール

# コンパイラ
gcc --version  # または clang --version
# GCC 9以上、Clang 10以上を推奨

# デバッガ
gdb --version  # Linux
lldb --version # macOS

# メモリチェッカー
valgrind --version  # Linux(macOSは非対応、代替あり)

# ビルドツール
make --version

# コードフォーマッタ(42 Norm準拠)
norminette --version  # 42の公式ツール

1.6.2 プロジェクト構造

libft/
├── Makefile          # ビルド設定
├── libft.h           # ヘッダファイル(関数宣言)
├── ft_isalpha.c      # 各関数の実装
├── ft_isdigit.c
├── ft_strlen.c
├── ...
└── tests/            # テストファイル(任意)
    ├── test_main.c
    └── ...

1.6.3 Makefile の基本

# libft/Makefile

NAME = libft.a

CC = gcc
CFLAGS = -Wall -Wextra -Werror

SRCS = ft_isalpha.c ft_isdigit.c ft_strlen.c # ... その他
OBJS = $(SRCS:.c=.o)

all: $(NAME)

$(NAME): $(OBJS)
	ar rcs $(NAME) $(OBJS)

%.o: %.c
	$(CC) $(CFLAGS) -c ___CODE_BLOCK_25___lt; -o $@

clean:
	rm -f $(OBJS)

fclean: clean
	rm -f $(NAME)

re: fclean all

.PHONY: all clean fclean re

1.7 まとめと次章への展望

本章では、以下のことを学びました:

  • コンピュータの歴史: パスカリーヌからENIAC、そしてフォン・ノイマン・アーキテクチャへの発展
  • 二進法とデータ表現: なぜコンピュータは0と1を使うのか、整数と浮動小数点の表現
  • C言語の設計哲学: 効率性、シンプルさ、プログラマへの信頼
  • コンパイルの仕組み: ソースコードから実行ファイルへの変換過程
  • Libftの意義: なぜ標準ライブラリを再実装するのか

次の第2章では、文字エンコーディングの世界を深く掘り下げます。ASCIIコードの歴史と設計、Unicodeへの進化、そしてビット演算の数学的基礎を学んだ上で、ft_isalphaft_isdigitなどの文字処理関数の実装に進みます。

コンピュータの内部では、文字は単なる数値として扱われます。この事実を深く理解することで、文字処理関数の実装が「なぜそうなるのか」が自然と見えてくるでしょう。