第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 まとめ
本章では、コンパイルとリンクについて学んだ:
次章では、デバッグと高度なトピックについて学ぶ。
---