第1章:C言語標準規格の読み方

この章で学ぶこと

  • C言語標準規格の歴史的進化
  • ISO/IEC 9899の構造と読み方
  • 規範的要求と参考情報の違い
  • shall, should, may の厳密な意味
  • 規格文書の入手と活用法

---

1.1 C言語標準規格の歴史

K&R時代(〜1989年)

C言語は1972年にデニス・リッチーによってベル研究所で開発されました。初期のCには公式な規格がなく、1978年に出版された「The C Programming Language」(通称K&R本)が事実上の標準でした。

K&R Cの特徴:

  • 関数プロトタイプなし
  • int の暗黙宣言
  • 可変長引数のサポートなし
  • 厳密な型チェックの欠如

/* K&R スタイル */
main()
{
    printf("Hello\n");  /* 宣言なしでも使用可能 */
}

double avg(a, b)
double a, b;  /* パラメータの型は後から宣言 */
{
    return (a + b) / 2;
}

ANSI C / C89(1989年)

アメリカ規格協会(ANSI)がC言語を標準化し、ANSI X3.159-1989として公開されました。翌年、国際標準化機構(ISO)がISO/IEC 9899:1990として採用しました(内容はほぼ同一)。

C89の主な追加:

  • 関数プロトタイプ
  • void
  • constvolatile 修飾子
  • プリプロセッサの標準化
  • 標準ライブラリの規定

/* ANSI C スタイル */
#include <stdio.h>

double avg(double a, double b);  /* プロトタイプ */

int main(void)
{
    printf("%.2f\n", avg(3.0, 5.0));
    return 0;
}

double avg(double a, double b)
{
    return (a + b) / 2;
}

C99(1999年)

ISO/IEC 9899:1999は、C言語に大幅な機能追加を行いました。

C99の主な追加:

  • // コメント
  • 混合宣言と文
  • 可変長配列(VLA)
  • inline 関数
  • _Bool
  • long long int
  • restrict 修飾子
  • 指示初期化子
  • 複合リテラル
  • , ,

/* C99 スタイル */
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>

int main(void)
{
    // C++スタイルのコメント
    bool flag = true;
    int32_t fixed = 42;

    for (int i = 0; i < 10; i++) {  // 混合宣言
        int n = 5;
        int arr[n];  // VLA
        arr[0] = i;
    }

    struct point { int x, y; };
    struct point p = { .y = 10, .x = 20 };  // 指示初期化子

    return 0;
}

C11(2011年)

ISO/IEC 9899:2011は、並行処理とセキュリティに焦点を当てました。

C11の主な追加:

  • _Atomic 型修飾子と
  • _Thread_local ストレージクラス
  • _Generic 選択
  • 無名構造体・共用体
  • static_assert
  • Bounds checking interfaces(Annex K)
  • アラインメント指定(_Alignas, _Alignof
  • (オプション)

/* C11 スタイル */
#include <stdatomic.h>
#include <stdalign.h>

_Static_assert(sizeof(int) >= 4, "int must be at least 4 bytes");

_Atomic int counter = 0;

struct data {
    alignas(16) double values[4];
    struct {  // 無名構造体
        int x, y;
    };
};

#define print_type(x) _Generic((x), \
    int: "int", \
    double: "double", \
    default: "unknown")

C17/C18(2018年)

ISO/IEC 9899:2018は、C11の欠陥報告を修正した保守版です。新機能は追加されていませんが、多くの曖昧さが解消されました。

C23(2024年)

最新の規格であるISO/IEC 9899:2024は、多くの新機能を追加しました。

C23の主な追加:

  • typeoftypeof_unqual
  • constexpr オブジェクト
  • #embed ディレクティブ
  • _BitInt(N)
  • nullptrnullptr_t
  • truefalse がキーワードに
  • auto 型推論
  • ラベル前の宣言許可
  • [[]] 属性構文

/* C23 スタイル */
#include <stddef.h>

constexpr int SIZE = 100;
auto ptr = nullptr;  // auto型推論

typeof(1 + 2.0) x;  // double

_BitInt(128) big_int;

[[maybe_unused]] int unused_var;
[[nodiscard]] int important_function(void);

static const unsigned char icon[] = {
    #embed "icon.png"
};

---

1.2 規格文書の構造

ISO/IEC 9899の章構成

C言語規格は以下の章から構成されています:

| 章 | タイトル | 内容 | |----|----------|------| | 1 | Scope | 規格の適用範囲 | | 2 | Normative references | 引用規格 | | 3 | Terms, definitions, and symbols | 用語定義 | | 4 | Conformance | 適合性要件 | | 5 | Environment | 実行環境 | | 6 | Language | 言語仕様(核心部分) | | 7 | Library | 標準ライブラリ |

Annexes(付属書)

  • Annex A: Language syntax summary(参考)
  • Annex B: Library summary(参考)
  • Annex C: Sequence points(参考)
  • Annex D: Universal character names(規範)
  • Annex E: Implementation limits(参考)
  • Annex F: IEC 60559 floating point(規範、条件付き)
  • Annex G: IEC 60559 complex(規範、条件付き)
  • Annex H: Language independent arithmetic(参考)
  • Annex I: Common warnings(参考)
  • Annex J: Portability issues(参考)
  • Annex K: Bounds-checking interfaces(規範、オプション)
  • Annex L: Analyzability(規範、条件付き)

規範的(Normative)と参考(Informative)

規範的(Normative):適合実装が遵守すべき要件。本文と規範的付属書が該当します。

参考(Informative):理解を助けるための補足情報。違反しても適合性には影響しません。

規格文書中の注釈:
NOTE — これは参考情報です
EXAMPLE — これも参考情報です

---

1.3 shall, should, may の意味

shall(〜しなければならない)

shall は絶対的要件を示します。適合実装はこれに従う必要があります。

> The value of an object shall not be accessed by an expression of a type other than the following...

「〜してはならない」は shall not で表現されます。

should(〜すべきである)

should は推奨を示します。従うことが強く推奨されますが、絶対ではありません。

> Implementations should diagnose...

may(〜してもよい)

may は許可を示します。実装はそうしてもしなくてもよいです。

> An implementation may define macros...

制約(Constraints)と意味規則(Semantics)

Constraints セクションの shall 違反は、コンパイル時診断が必要です。

Semantics セクションの shall 違反は、未定義動作になります。

/* Constraints 違反の例 */
int arr[5];
arr = arr;  /* 診断が必要:配列は代入不可 */

/* Semantics 違反の例 */
int *p = NULL;
*p = 42;  /* 未定義動作:NULLの参照外し */

---

1.4 動作の分類

未定義動作(Undefined Behavior)

規格が何の要件も課さない動作。何が起きても構いません。

> undefined behavior: behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this document imposes no requirements

典型例:

  • NULLポインタの参照外し
  • 配列境界外アクセス
  • 符号付き整数オーバーフロー
  • 初期化されていない変数の使用

int arr[5];
int x = arr[10];  /* 未定義動作 */

未規定動作(Unspecified Behavior)

規格が複数の選択肢を許容し、どれが選ばれるか規定しない動作。

> unspecified behavior: behavior, that results from the use of an unspecified value, or other behavior upon which this document provides two or more possibilities and imposes no further requirements

典型例:

  • 関数引数の評価順序
  • sizeof が返す値の型(ただし size_t

int f(void) { printf("f\n"); return 1; }
int g(void) { printf("g\n"); return 2; }

int x = f() + g();  /* f()とg()の評価順序は未規定 */

処理系定義動作(Implementation-Defined Behavior)

未規定動作の中で、実装が選択肢を文書化する義務があるもの。

> implementation-defined behavior: unspecified behavior where each implementation documents how the choice is made

典型例:

  • char が符号付きか符号なしか
  • int のビット数
  • 右シフトが論理か算術か(負の値の場合)

char c = 128;  /* charが符号付きなら負の値になる可能性 */
int x = -1 >> 1;  /* 結果は処理系定義 */

ロケール固有動作(Locale-Specific Behavior)

現在のロケールに依存する動作。

> locale-specific behavior: behavior that depends on local conventions of nationality, culture, and language

典型例:

  • isalpha() の判定
  • strcoll() の照合順序

---

1.5 適合実装の要件

ホスト環境と独立環境

ホスト環境(Hosted):OS上で動作し、完全な標準ライブラリを提供。

独立環境(Freestanding):OS不要。最小限のヘッダのみ保証。

独立環境で保証されるヘッダ:

適合プログラム

厳密適合プログラム

  • 規格で指定された機能のみ使用
  • 未規定、未定義、処理系定義動作に依存しない
  • 処理系のリソース制限を超えない

適合プログラム

  • 適合実装で受け入れられる
  • 処理系定義動作への依存は許可
  • 診断メッセージ

    適合実装は、構文規則または制約の違反に対して、少なくとも1つの診断メッセージを出力しなければなりません。

    int main(void)
    {
        int x = "hello";  /* 制約違反:診断必須 */
        return 0;
    }
    

    ---

    1.6 規格文書の入手方法

    公式規格

    ISO/IEC 9899は有料(数百スイスフラン)ですが、以下の方法で内容を確認できます。

    最終ドラフト

    標準化直前のドラフトは無料で公開されています:

  • C11: N1570 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)
  • C17: N2176 (最終ドラフトは非公開だがN2176が近い)
  • C23: N3096 (https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf)
  • 最終ドラフトと公式規格の差異は通常、編集上の修正のみです。

    欠陥報告(Defect Reports)

    規格の解釈や欠陥に関する公式回答は、WG14のウェブサイトで公開されています。

    https://www.open-std.org/jtc1/sc22/wg14/

    有用なリソース

  • cppreference.com: 規格の内容を分かりやすく解説
  • GCC Manual: GCCの実装詳細と拡張
  • Clang Documentation: Clangの実装詳細
  • ---

    1.7 規格を読むためのヒント

    相互参照の活用

    規格は内部で多くの相互参照を含んでいます。

    > As specified in 6.2.4, an object has a storage duration...

    章番号をたどって定義を確認しましょう。

    用語の厳密な理解

    日常的な意味と技術的な定義が異なることがあります。

  • オブジェクト: メモリ領域(OOPのオブジェクトではない)
  • 左辺値: オブジェクトを指定する式
  • : オブジェクトの性質と許容される操作の集合
  • 例外に注意

    「except」や「unless」を見逃さないようにしましょう。

    > An identifier declared in different scopes... may refer to the same object... if one of them has external linkage and the other has no linkage.

    Annex J.2の活用

    Annex J.2には未定義動作の一覧があり、非常に有用です。

    ---

    1.8 この章のまとめ

    学んだこと

  • 規格の歴史: K&R → C89 → C99 → C11 → C17 → C23
  • 文書構造: 本文、付属書、規範/参考
  • 用語: shall/should/may の厳密な意味
  • 動作分類: 未定義/未規定/処理系定義/ロケール固有
  • 適合性: ホスト/独立環境、厳密適合/適合プログラム
  • 入手方法: 最終ドラフト、欠陥報告

次の章の予告

次章では、型システムの仕様レベル理解を学びます。整数昇格、通常の算術型変換、互換型など、規格に基づく厳密な定義を解説します。

---

Column: なぜ規格を読むのか

「動くコード」と「正しいコード」

コンパイルが通り、テストが成功するコードが「正しい」とは限りません。

int main(void)
{
    int x = INT_MAX;
    x = x + 1;  /* 未定義動作 */
    if (x < 0)
        printf("Overflow detected!\n");
    return 0;
}

このコードは多くの環境で「動く」かもしれませんが、規格上は未定義動作です。コンパイラは最適化により予期しないコードを生成する可能性があります。

コンパイラは敵ではない

未定義動作を利用した最適化は、コンパイラのバグではありません。規格に従った正当な動作です。

規格を読むことで:

  • コードが本当に正しいか判断できる
  • 移植性の問題を予見できる
  • コンパイラの動作を予測できる
  • 効率的で正確なコードを書ける

プロフェッショナルの条件

「この挙動は保証されているのか?」という問いに、規格を参照して答えられることがプロフェッショナルの条件です。

経験や直感だけでなく、規範的な根拠に基づいてコードを書きましょう。

---

確認問題

問題1

C11で追加された機能を3つ挙げてください。

解答

以下のいずれか3つ:

  • _Atomic 型修飾子と
  • _Thread_local ストレージクラス
  • _Generic 選択
  • 無名構造体・共用体
  • static_assert
  • Bounds checking interfaces(Annex K)
  • アラインメント指定(_Alignas, _Alignof

問題2

「shall」と「should」の違いは何ですか?

解答

  • shall: 絶対的要件。適合実装は必ず従わなければならない。
  • should: 推奨。従うことが強く推奨されるが、絶対ではない。

問題3

未定義動作と処理系定義動作の違いを説明してください。

解答

  • 未定義動作: 規格が何の要件も課さない。何が起きてもよい。
  • 処理系定義動作: 実装が複数の選択肢から1つを選び、文書化する義務がある。

問題4

Constraints セクションの shall 違反はどうなりますか?

解答

コンパイル時に診断メッセージが必須。(適合実装は診断を出力しなければならない)

問題5

C11の無料で入手できる最終ドラフトは?

解答

N1570(https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)

---

次の章では、型システムの仕様レベル理解を学びます。