Skip to content
Published on

[コンパイラ] 02. プログラミング言語の基礎とコンパイラ技術の応用

Authors

プログラミング言語の基礎とコンパイラ技術の応用

コンパイラを正しく理解するには、プログラミング言語自体に関する基礎知識が必要です。この記事では、言語の発展過程から核心概念、そしてコンパイラ技術がコンパイル以外の分野でどのように活用されるかを扱います。


1. プログラミング言語の発展

第1世代: 機械語(1940年代)

コンピュータが直接実行できるバイナリコードです。人間が直接書くのは非常に困難です。

10110000 01100001

第2世代: アセンブリ言語(1950年代)

機械語を記号(ニーモニック)で表現した低水準言語です。

MOV AL, 61h
ADD AL, BL

第3世代: 高水準言語(1960年代〜)

Fortran、COBOL、C、Javaなど、人間が理解しやすい言語が登場しました。

int result = a + b;

第4世代以降

SQLのようなドメイン特化言語(DSL)やPython、JavaScriptのようなスクリプト言語が含まれます。

言語設計とコンパイラの相互作用

プログラミング言語の設計とコンパイラ技術は互いに影響を与え合います。

  • Fortran: 効率的な数値演算コンパイルが目標でした。
  • Java: バイトコードとJITコンパイラによる移植性を重視しました。
  • Rust: コンパイル時の所有権検査でメモリ安全性を保証します。

2. 静的(Static)vs 動的(Dynamic)の区分

コンパイラ理論において静的とはコンパイル時に決定されることを、動的とは実行時に決定されることを意味します。

静的型付け言語

変数の型がコンパイル時に決定されます。

int x = 10;         // xはコンパイル時にintとして確定
String s = "hello"; // sはコンパイル時にStringとして確定
// x = "world";     // コンパイルエラー!

利点: 型エラーを実行前に発見でき、最適化が容易です。

動的型付け言語

変数の型が実行時に決定されます。

x = 10        # xは現在int
x = "hello"   # xがstrに変更(エラーなし)

利点: 柔軟で迅速な開発が可能です。

静的/動的の比較

            コンパイル時            実行時
           (Static)              (Dynamic)
型決定:    Java, C, Rust         Python, JS, Ruby
バインディング: 静的バインディング    動的バインディング
ディスパッチ: 静的ディスパッチ       動的ディスパッチ(仮想関数)
メモリ:    スタック割り当て決定    ヒープ割り当て決定

3. スコープ規則(Scope Rules)

**スコープ(scope)**とはプログラム内で変数宣言が有効な範囲のことです。

静的スコープ(Static/Lexical Scope)

変数のスコープがソースコードの構造によって決定されます。現代のほとんどの言語(C、Java、Pythonなど)が静的スコープを使用します。

int x = 10;

void foo() {
    printf("%d", x);  // 常にグローバル変数 x = 10 を参照
}

void bar() {
    int x = 20;
    foo();  // foo内のxは依然として10
}

静的スコープでは関数が定義された位置で変数を探します。

動的スコープ(Dynamic Scope)

変数のスコープが実行時の呼び出し順序によって決定されます。一部のLisp方言やBashスクリプトが動的スコープを使用します。

#!/bin/bash
x=10

foo() {
    echo $x  # 呼び出し時点のxの値を参照
}

bar() {
    local x=20
    foo   # 動的スコープなのでx = 20を出力
}

bar  # 出力: 20

動的スコープでは関数が呼び出された位置で変数を探します。

ブロック構造とスコープ

C系言語ではブロック({...})が新しいスコープを作ります。

void example() {
    int x = 1;
    {
        int x = 2;  // 内部ブロックのx(外部xを隠す)
        printf("%d\n", x);  // 2を出力
    }
    printf("%d\n", x);  // 1を出力(外部x)
}

コンパイラは**スコープチェーン(scope chain)**を通じて変数名を解決します。現在のスコープで見つからなければ外側のスコープを順番に探索します。


4. パラメータ渡し方式(Parameter Passing)

関数に引数を渡す方式は言語によって異なります。

値渡し(Call by Value)

引数の値をコピーして関数に渡します。

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    // a, bはコピーなので元の値に影響なし
}

int main() {
    int x = 1, y = 2;
    swap(x, y);
    // x = 1, y = 2(変更なし)
}

参照渡し(Call by Reference)

引数のメモリアドレスを渡します。

void swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
    // a, bは元の変数の別名なので元の値が変更される
}

int main() {
    int x = 1, y = 2;
    swap(x, y);
    // x = 2, y = 1(変更された)
}

名前渡し(Call by Name)

引数の式そのものを渡し、使用するたびに再評価します。Algol 60で使用され、現代の言語ではほとんど使用されませんが、Scalaの => T 構文が類似の概念です。

def condition(test: => Boolean, msg: => String): Unit = {
    if (test) println(msg)  // msgはtestがtrueの時だけ評価される
}

パラメータ渡し方式の比較

+------------------+----------+----------+------------------+
| 方式              | コピー費用| 元の値変更| 代表的な言語      |
+------------------+----------+----------+------------------+
| Call by Value    | あり     | 不可     | C, Java(基本型)  |
| Call by Reference| なし     | 可能     | C++, C#(ref)     |
| Call by Name     | なし     | 可能     | Algol 60, Scala  |
+------------------+----------+----------+------------------+

5. 環境(Environment)と状態(State)

コンパイラ理論における重要な2つのマッピングがあります。

名前(name) --[環境]--> 格納場所(location) --[状態]--> 値(value)

例:
  "x"  --[環境]--> 0x7fff1234  --[状態]--> 42
  • 環境(Environment): 名前を格納場所にマッピングします。変数宣言時に生成されます。
  • 状態(State): 格納場所を値にマッピングします。代入文実行時に変更されます。

この区分はポインタと参照を理解する上で重要です。同じ名前が異なる環境で異なる場所を指すことができ(スコープ)、同じ場所の値はプログラム実行中に変わることがあります(状態変更)。


6. コンパイラ技術の応用分野

コンパイラ技術はプログラミング言語の翻訳以外にも様々な分野で活用されています。

IDEツール

現代のIDE(IntelliJ、VS Codeなど)はコンパイラ技術を使用しています。

  • シンタックスハイライト(Syntax Highlighting): 字句解析技術を活用します。
  • コード自動補完: 構文解析と型分析の結果を利用します。
  • リファクタリング: 構文木変換技術を適用します。
  • リアルタイムエラー表示: インクリメンタルパーシング(incremental parsing)技術を使用します。

セキュリティ分析

  • 静的解析(Static Analysis): コードを実行せずに潜在的な脆弱性を検出します。
  • バッファオーバーフロー検出: データフロー分析を通じてバッファサイズ超過を検査します。
  • 汚染分析(Taint Analysis): 外部入力が危険な関数に到達するかを追跡します。
[セキュリティ静的解析の流れ]
  ソースコード --> [構文解析] --> [データフロー分析] --> [脆弱性報告]

プログラム検証

コンパイラの意味解析技術を拡張してプログラムが仕様を満たすか検証します。

  • モデル検査(Model Checking): 状態空間探索による属性検証
  • 抽象解釈(Abstract Interpretation): プログラムの近似的意味解析
  • 依存型(Dependent Types): AgdaやIdrisなどの言語で型を通じて属性を証明

その他の応用分野

  • 自然言語処理(NLP): 文法分析に構文解析技法を活用します。
  • ハードウェア合成: VHDLやVerilogコンパイラがハードウェア回路を生成します。
  • データベースクエリ: SQLパーサとクエリ最適化にコンパイラ技術を使用します。
  • ドメイン特化言語(DSL): コンパイラフロントエンド技術でカスタム言語を構築します。

まとめ

概念核心内容
静的 vs 動的コンパイル時に決定されれば静的、実行時に決定されれば動的
静的スコープコードの構造(定義位置)で変数を解決
動的スコープ実行時の呼び出し順序で変数を解決
値渡し値をコピー、元の値は不変
参照渡しアドレスを渡す、元の値の変更可能
環境 vs 状態名前を場所に、場所を値にマッピング
コンパイラ応用IDE、セキュリティ分析、プログラム検証、DSLなど

プログラミング言語の基本概念を理解することは、コンパイラの各段階がなぜ必要なのかを理解するために不可欠です。次の記事では、簡単な構文指示翻訳器を実際に作りながら、コンパイラの核心原理を体験してみます。