C

OS や組み込みを支える低レベル言語。多くの言語の祖となった基礎的存在

プログラミング言語低レベル
C」の技術書を見る →

C とは

C 言語は、Unix を書くための言語として Bell 研究所の Dennis Ritchie が作った汎用プログラミング言語だ。Ritchie 自身の解説によれば、C が形になったのは 1969 年から 1973 年にかけてで、最も変化が大きかったのは 1972 年だった。型を持たない BCPL から派生した B に型の仕組みを与えたところが出発点にある。ハードウェアに近い低レベルな操作ができながら、人間が読み書きしやすい構文を備える。OS (UNIX/Linux)、組み込み機器、各種言語の処理系など、コンピュータの土台となるソフトウェアの多くが C で書かれている。

C++・Objective-C・Java・C#・Go のように、後から出た多くの言語が中括弧の構文や演算子の書き方を C から引き継いだため、しばしば祖先として語られる。ただし Lisp・Fortran・Smalltalk のように C より前から別系統で発展した言語もあるので、「すべての言語の祖」ではない。影響が大きい系統の起点、という理解が正確だ。

なぜ今も重要か

用途理由
OS・カーネルハードウェアを直接制御できる
組み込み・IoT限られた資源で高速に動く
言語処理系多くの言語が C で実装される
学習コンピュータの動作原理が学べる

メモリやポインタを直接扱うため、コンピュータが内部でどう動くかを理解するのに最適な言語でもある。

規格の系譜と「どの C」で書くか

1978 年に Brian Kernighan と Ritchie が書いた『The C Programming Language』(通称 K&R) が、正式な規格ができるまで 10 年以上のあいだ事実上の言語定義として使われた。規格化は 1983 年夏に発足した ANSI X3J11 委員会が担い、1989 年末にまとまった報告書が最初の公式規格となり、これがそのまま ISO に受け入れられて ISO/IEC 9899-1990 になった。以後は同じ ISO/IEC 9899 の改訂として続いている。

位置づけ
K&R (1978 年の書籍)規格制定前の事実上の言語定義
ANSI C (1989 年末)最初の公式規格・翌年に ISO/IEC 9899-1990 として ISO 化
C99 以降ISO/IEC 9899 の改訂として継続
C23最新版・2024 年に ISO と IEC が採択 (規格委員会 WG14 の記述・2026 年 8 月時点)

注意したいのは、処理系の既定が最新規格とは限らないことだ。Apple clang 21 では、規格を指定せずにコンパイルすると __STDC_VERSION__201710L (C17) を返し、-std=c23 を付けて初めて 202311L になる。どの版で書くのかは必ずコンパイルオプションで明示し、チームと CI で揃えておく。「手元では通るのに別環境で落ちる」の典型的な原因がここにある。

C++ との関係

C++ は C を拡張してオブジェクト指向などを加えた言語で、両者はしばしば比較される。C はシンプルさを保ち、機能を絞ることで小ささと移植性を維持している。一方 C++ は大規模開発向けの高度な機能を持つ。「最小限の制御」を求めるなら C、「制御に加えて大規模な抽象化」を求めるなら C++ という棲み分けになる。

学ぶうえでの注意点

C の自由度は強力だが危険でもある。メモリを手動で管理するため、確保・解放のミスがクラッシュやセキュリティ脆弱性 (バッファオーバーフローなど) に直結する。現代の高水準言語が自動でやってくれることを、C では自分で正しく行う責任がある。だからこそ学ぶ価値が大きく、コンピュータの仕組みを肌で理解する糧になる。実務での新規開発に使うかは、性能・移植性の要件次第で判断したい。

未定義動作という落とし穴

C の危険さは、書き間違いがその場で止まってくれない点にある。規格が結果を定めていない操作 (未定義動作) を書くと、コンパイラは「その状況は起こらない」と仮定して最適化してよい。だから最適化の強さを変えるだけで実行結果が変わる。

#include <stdio.h>
#include <limits.h>

int grows(int x) { return x + 1 > x; }

int main(void) { printf("%d\n", grows(INT_MAX)); return 0; }

符号付き整数のオーバーフローは未定義動作なので、x + 1 > x は桁があふれれば偽になり得るのに、コンパイラは常に真と見なして定数へ畳み込める。Apple clang 21 で試すと -O0 では 0-O2 では 1 が出力された。同じソースが最適化の指定だけで逆の答えを返す。バグが再現しない、リリースビルドだけ壊れるという厄介な症状はこうして生まれる。

対処は 2 段構えになる。書く側では、あふれないことを先に確かめてから足す (x < INT_MAX を判定してから x + 1 を評価する)。動かす側では検出器を有効にする。-fsanitize=undefined を付けて実行すると、上の例は runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int' と発生位置付きで報告される。解放後の参照やバッファ越えは -fsanitize=address が担当し、heap-use-after-free としてアドレスとともに報告される。テストが通ったことを安全の根拠にせず、検出器を通した実行を習慣にしたい。

今から C を選ぶかの判断

新規開発で C を選ぶかは、次の 3 点で切り分けると迷いにくい。

  1. その環境で他に選択肢があるか。 OS のカーネルや既存のカーネルモジュール、資源の限られたマイコン、他言語から呼ばれる共有ライブラリのように、C の呼び出し規約や実行環境が前提になっている領域では C が最短経路になる。
  2. メモリ安全性の責任を負う体制があるか。 確保・解放と境界の管理を人が担う以上、検出器付きの継続的なテストとレビューが要る。それを用意できないなら、同じ低レベル領域でも所有権の検査を処理系に任せられる言語や C++ を先に検討する。
  3. 移植性がどこまで必要か。 規格を明示し処理系依存を避けて書けば、C は動かせる対象機の幅広さで今も抜きん出ている。逆に単一環境で完結する用途なら、あえて C を選ぶ動機は薄い。

学ぶ対象としての価値は、この判断とは別に残る。ポインタと手動のメモリ管理を一度自分で書いておくと、他の言語が裏で何を代行しているかが見えるようになる。

この記事は役に立ちましたか?

関連用語

関連する記事