第11章: コンパイルとリンク

11.1 コンパイルの理論

コンパイラとは

コンパイラ(Compiler)は、高水準言語で書かれたソースコードを機械語に変換するプログラムである。

歴史的背景:

  • 1952年、Grace Hopperが最初のコンパイラ(A-0)を開発
  • 1957年、FORTRANコンパイラがIBMで開発
  • C言語コンパイラは1972年、Dennis RitchieによってPDP-11上で最初に実装
  • コンパイラの役割:
    
    ソースコード (高水準)     機械語 (低水準)
    ┌─────────────────┐      ┌─────────────────┐
    │ int main() {    │      │ 55              │
    │   return 0;     │  →   │ 48 89 e5        │
    │ }               │      │ b8 00 00 00 00  │
    │                 │      │ 5d              │
    └─────────────────┘      │ c3              │
                             └─────────────────┘
    

    コンパイルの4つのフェーズ

    ┌─────────────────┐
    │   ソースコード   │  (.c)
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │  プリプロセッサ  │  #include, #define を処理
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │    翻訳単位     │  (.i) 純粋なCコード
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │   コンパイル    │  字句解析 → 構文解析 → 意味解析 → 最適化 → コード生成
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │  アセンブリコード │  (.s)
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │   アセンブラ    │
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │ オブジェクトファイル │  (.o)
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │    リンカ       │  複数の.oを結合
    └────────┬────────┘
             ↓
    ┌─────────────────┐
    │   実行ファイル   │  (a.out, .exe)
    └─────────────────┘
    

    11.2 コンパイラの内部構造

    字句解析(Lexical Analysis)

    ソースコードをトークン(Token)に分割する。

    /* ソースコード */
    int x = 42;
    
    /* トークン列 */
    [KEYWORD:int] [IDENTIFIER:x] [OPERATOR:=] [INTEGER:42] [PUNCTUATION:;]
    

    構文解析(Syntax Analysis)

    トークン列から抽象構文木(AST: Abstract Syntax Tree)を構築する。

    int x = 42; の抽象構文木:
    
            VariableDecl
            ┌────┴────┐
           Type      Init
            │         │
           int    Assignment
                  ┌────┴────┐
               Identifier  Literal
                  │          │
                  x         42
    

    意味解析(Semantic Analysis)

    型チェック、スコープ解決、暗黙の型変換を行う。

    int x = "hello";  /* 型エラー: char* を int に代入できない */
    
    float f = 3.14;
    int i = f;        /* 警告: 暗黙の float → int 変換 */
    

    最適化(Optimization)

    コードの実行効率を向上させる変換を行う。

    /* 最適化前 */
    int foo(int x) {
        int a = x * 2;
        int b = a + a;
        return b;
    }
    
    /* 最適化後(定数畳み込み、コピー伝播) */
    int foo(int x) {
        return x * 4;
    }
    

    コード生成(Code Generation)

    最適化された中間表現からアセンブリコードを生成する。

    ; int foo(int x) { return x * 4; }
    foo:
        lea eax, [rdi*4]    ; x * 4 を計算
        ret
    

    11.3 GCCの使用

    基本的なコンパイル

    # シンプルなコンパイル
    gcc main.c -o main
    
    # 複数ファイル
    gcc main.c utils.c -o program
    
    # ヘッダファイルの検索パスを追加
    gcc -I./include main.c -o main
    
    # ライブラリのリンク
    gcc main.c -lm -o main      # 数学ライブラリ
    gcc main.c -lpthread -o main # POSIXスレッド
    

    コンパイルフェーズの分離

    # プリプロセスのみ
    gcc -E main.c -o main.i
    
    # コンパイルのみ(アセンブリ生成)
    gcc -S main.c -o main.s
    
    # アセンブルまで(オブジェクト生成)
    gcc -c main.c -o main.o
    
    # リンク
    gcc main.o utils.o -o program
    

    警告オプション

    # 基本的な警告
    gcc -Wall main.c           # 一般的な警告を有効化
    gcc -Wextra main.c         # 追加の警告
    gcc -Werror main.c         # 警告をエラーとして扱う
    
    # 推奨される組み合わせ
    gcc -Wall -Wextra -Werror -pedantic main.c
    
    # 個別の警告
    gcc -Wunused-variable main.c        # 未使用変数
    gcc -Wuninitialized main.c          # 未初期化変数
    gcc -Wshadow main.c                 # 変数のシャドウイング
    gcc -Wformat main.c                 # printf/scanf フォーマット
    gcc -Wconversion main.c             # 暗黙の型変換
    gcc -Wsign-compare main.c           # 符号付き/なしの比較
    

    最適化オプション

    # 最適化レベル
    gcc -O0 main.c    # 最適化なし(デフォルト)
    gcc -O1 main.c    # 基本的な最適化
    gcc -O2 main.c    # 推奨される最適化レベル
    gcc -O3 main.c    # 積極的な最適化(サイズ増加の可能性)
    gcc -Os main.c    # サイズ最適化
    gcc -Ofast main.c # 速度最優先(標準非準拠の最適化含む)
    
    # 個別の最適化
    gcc -finline-functions main.c  # 関数のインライン化
    gcc -funroll-loops main.c      # ループの展開
    gcc -fomit-frame-pointer main.c # フレームポインタの省略
    

    デバッグオプション

    # デバッグ情報を含める
    gcc -g main.c -o main
    
    # デバッグレベル
    gcc -g0 main.c    # デバッグ情報なし
    gcc -g1 main.c    # 最小限
    gcc -g2 main.c    # デフォルト(-g と同じ)
    gcc -g3 main.c    # マクロ情報も含む
    
    # プロファイリング用
    gcc -pg main.c -o main  # gprof 用
    

    C標準の指定

    # C標準を指定
    gcc -std=c89 main.c     # C89/C90
    gcc -std=c99 main.c     # C99
    gcc -std=c11 main.c     # C11
    gcc -std=c17 main.c     # C17
    gcc -std=c2x main.c     # C23(ドラフト)
    
    # GNU拡張を含む
    gcc -std=gnu99 main.c   # C99 + GNU拡張
    gcc -std=gnu11 main.c   # C11 + GNU拡張
    

    11.4 リンク

    リンカの役割

    リンカの処理:
    
    ┌─────────┐  ┌─────────┐  ┌─────────┐
    │ main.o  │  │ utils.o │  │ libc.a  │
    └────┬────┘  └────┬────┘  └────┬────┘
         │            │            │
         └────────────┼────────────┘
                      ↓
             ┌───────────────┐
             │    リンカ      │
             └───────┬───────┘
                     ↓
             ┌───────────────┐
             │   実行ファイル  │
             └───────────────┘
    
    リンカの主な仕事:
    1. シンボル解決(Symbol Resolution)
    2. 再配置(Relocation)
    3. セクションのマージ
    

    シンボル

    /* main.c */
    extern int global_var;        /* 外部シンボル参照 */
    extern void helper(void);     /* 外部関数参照 */
    
    int main_var = 100;           /* グローバルシンボル定義 */
    
    int main(void)
    {
        helper();
        return global_var + main_var;
    }
    
    /* utils.c */
    int global_var = 42;          /* グローバルシンボル定義 */
    
    void helper(void)             /* グローバルシンボル定義 */
    {
        /* ... */
    }
    

    シンボルテーブルの確認

    # オブジェクトファイルのシンボルを表示
    nm main.o
    # U helper       (Undefined: 未定義、外部参照)
    # U global_var   (Undefined)
    # 0000000000000000 T main     (Text: コードセクションの定義)
    # 0000000000000000 D main_var (Data: データセクションの定義)
    
    # より詳細な情報
    objdump -t main.o
    
    # 動的シンボル
    nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep printf
    

    静的ライブラリ

    # 静的ライブラリの作成
    gcc -c utils.c -o utils.o
    gcc -c helper.c -o helper.o
    ar rcs libmylib.a utils.o helper.o
    
    # アーカイブの内容を確認
    ar -t libmylib.a
    
    # 静的ライブラリのリンク
    gcc main.c -L. -lmylib -o program
    # または
    gcc main.c ./libmylib.a -o program
    

    動的ライブラリ(共有ライブラリ)

    # 共有ライブラリの作成
    gcc -fPIC -c utils.c -o utils.o    # PIC: Position Independent Code
    gcc -shared -o libmylib.so utils.o
    
    # 共有ライブラリのリンク
    gcc main.c -L. -lmylib -o program
    
    # 実行時のライブラリパス
    export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
    ./program
    
    # または rpath を埋め込む
    gcc main.c -L. -lmylib -Wl,-rpath,. -o program
    

    リンク順序

    # 重要: ライブラリは参照する側の後に指定
    gcc main.c -lmylib  # OK: main.c が libmylib を参照
    gcc -lmylib main.c  # エラーの可能性: 未解決シンボル
    
    # 相互依存がある場合
    gcc main.c -la -lb -la  # libla と liblb が相互参照
    # または
    gcc main.c -Wl,--start-group -la -lb -Wl,--end-group
    

    11.5 Make

    Makefileの基本

    # 基本的なMakefile
    CC = gcc
    CFLAGS = -Wall -Wextra -g
    LDFLAGS =
    
    # ターゲット: 依存関係
    #     レシピ(タブでインデント必須)
    
    program: main.o utils.o
    	$(CC) $(LDFLAGS) -o $@ $^
    
    main.o: main.c utils.h
    	$(CC) $(CFLAGS) -c -o $@ ___CODE_BLOCK_19___lt;
    
    utils.o: utils.c utils.h
    	$(CC) $(CFLAGS) -c -o $@ ___CODE_BLOCK_19___lt;
    
    clean:
    	rm -f *.o program
    
    .PHONY: clean
    

    自動変数

    $@    # ターゲット名
    ___CODE_BLOCK_20___lt;    # 最初の依存ファイル
    $^    # 全ての依存ファイル(重複なし)
    $?    # ターゲットより新しい依存ファイル
    $*    # パターンルールのステム(%にマッチした部分)
    

    パターンルール

    # パターンルール
    %.o: %.c
    	$(CC) $(CFLAGS) -c -o $@ ___CODE_BLOCK_21___lt;
    
    # 暗黙のルールを使用(上記と同等の効果)
    # make には組み込みのパターンルールがある
    

    変数の種類

    # 再帰的に展開される変数
    CC = gcc
    CFLAGS = $(WARNINGS)  # WARNINGS は後で定義可能
    
    # 単純展開変数
    CC := gcc
    CFLAGS := -Wall
    
    # 条件付き代入(未定義の場合のみ)
    CC ?= gcc
    
    # 追加代入
    CFLAGS += -g
    

    高度なMakefile

    # 変数
    CC := gcc
    CFLAGS := -Wall -Wextra -Werror -g
    LDFLAGS :=
    LDLIBS := -lm
    
    # ディレクトリ
    SRCDIR := src
    OBJDIR := obj
    BINDIR := bin
    
    # ソースファイルの自動検出
    SRCS := $(wildcard $(SRCDIR)/*.c)
    OBJS := $(SRCS:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
    TARGET := $(BINDIR)/program
    
    # デフォルトターゲット
    all: $(TARGET)
    
    # 実行ファイルの生成
    $(TARGET): $(OBJS) | $(BINDIR)
    	$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
    
    # オブジェクトファイルの生成
    $(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
    	$(CC) $(CFLAGS) -c -o $@ ___CODE_BLOCK_23___lt;
    
    # ディレクトリの作成
    $(BINDIR) $(OBJDIR):
    	mkdir -p $@
    
    # 依存関係の自動生成
    DEPS := $(OBJS:.o=.d)
    CFLAGS += -MMD -MP
    -include $(DEPS)
    
    # クリーン
    clean:
    	rm -rf $(OBJDIR) $(BINDIR)
    
    # フォニーターゲット
    .PHONY: all clean
    
    # デバッグ用
    debug:
    	@echo "SRCS: $(SRCS)"
    	@echo "OBJS: $(OBJS)"
    

    依存関係の自動生成

    # GCCで依存関係ファイルを生成
    gcc -MM main.c
    # main.o: main.c utils.h config.h
    
    gcc -MMD -c main.c  # .d ファイルも同時に生成
    

    11.6 分割コンパイル

    ヘッダファイルの設計

    /* utils.h */
    #ifndef UTILS_H
    #define UTILS_H
    
    /* 公開インターフェースのみ */
    void public_function(int x);
    extern int public_variable;
    
    /* 型定義 */
    typedef struct {
        int x;
        int y;
    } Point;
    
    #endif /* UTILS_H */
    

    /* utils.c */
    #include "utils.h"
    #include <stdio.h>
    
    /* 公開変数の定義 */
    int public_variable = 42;
    
    /* 非公開(静的)変数 */
    static int private_variable = 0;
    
    /* 非公開(静的)関数 */
    static void private_helper(void)
    {
        /* ... */
    }
    
    /* 公開関数の実装 */
    void public_function(int x)
    {
        private_variable += x;
        private_helper();
    }
    

    翻訳単位

    翻訳単位(Translation Unit):
    - プリプロセス後の1つの.cファイル
    - インクルードされた全ヘッダを含む
    - コンパイラはこの単位で処理
    
    main.c + stdio.h + utils.h → 1つの翻訳単位
    utils.c + utils.h          → 別の翻訳単位
    

    リンケージ

    /* 外部リンケージ(external linkage) */
    int global_var;            /* 他の翻訳単位から参照可能 */
    void global_func(void) {}  /* 他の翻訳単位から参照可能 */
    
    /* 内部リンケージ(internal linkage) */
    static int static_var;            /* この翻訳単位内のみ */
    static void static_func(void) {}  /* この翻訳単位内のみ */
    
    /* リンケージなし(no linkage) */
    void func(void) {
        int local_var;         /* ローカル変数 */
        static int static_local;  /* 静的ローカル(内部リンケージではない) */
    }
    

    インライン関数とリンケージ

    /* header.h */
    
    /* static inline: 各翻訳単位に独立したコピー */
    static inline int max(int a, int b)
    {
        return a > b ? a : b;
    }
    
    /* extern inline: 外部定義が必要 */
    /* header.h */
    inline int min(int a, int b)
    {
        return a < b ? a : b;
    }
    
    /* source.c */
    extern inline int min(int a, int b);  /* 外部定義を提供 */
    

    11.7 ビルドシステム

    CMake

    # CMakeLists.txt
    cmake_minimum_required(VERSION 3.10)
    project(MyProject C)
    
    # C標準
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_C_STANDARD_REQUIRED ON)
    
    # コンパイルオプション
    add_compile_options(-Wall -Wextra -Werror)
    
    # ソースファイル
    set(SOURCES
        src/main.c
        src/utils.c
    )
    
    # 実行ファイル
    add_executable(program ${SOURCES})
    
    # ライブラリのリンク
    target_link_libraries(program m pthread)
    
    # ヘッダの検索パス
    target_include_directories(program PRIVATE include)
    

    # CMakeの使用
    mkdir build && cd build
    cmake ..
    make
    

    Autotools

    # configure.ac
    AC_INIT([myproject], [1.0])
    AM_INIT_AUTOMAKE
    AC_PROG_CC
    AC_CONFIG_FILES([Makefile])
    AC_OUTPUT
    
    # Makefile.am
    bin_PROGRAMS = program
    program_SOURCES = main.c utils.c
    
    # ビルド
    autoreconf -i
    ./configure
    make
    

    11.8 クロスコンパイル

    # ARM用にクロスコンパイル
    arm-linux-gnueabihf-gcc main.c -o main_arm
    
    # Windows用(MinGW)
    x86_64-w64-mingw32-gcc main.c -o main.exe
    
    # CMakeでのクロスコンパイル
    cmake -DCMAKE_TOOLCHAIN_FILE=arm-toolchain.cmake ..
    

    11.9 まとめ

    本章では、コンパイルとリンクについて学んだ:

  • コンパイルの理論: 4つのフェーズ
  • コンパイラの内部: 字句解析、構文解析、最適化
  • GCCの使用: オプション、警告、最適化
  • リンク: シンボル、静的/動的ライブラリ
  • Make: Makefile、パターンルール
  • 分割コンパイル: ヘッダ設計、リンケージ
  • ビルドシステム: CMake, Autotools
  • 次章では、デバッグと高度なトピックについて学ぶ。

    ---

    参考文献

  • Levine, J. R. (1999). "Linkers and Loaders", Morgan Kaufmann
  • Stallman, R. M., et al. "GCC Manual", https://gcc.gnu.org/onlinedocs/
  • Mecklenburg, R. (2004). "Managing Projects with GNU Make", 3rd Edition, O'Reilly Media
  • ISO/IEC 9899:2011, Programming languages — C