Wikiwand AI

C言語

汎用プログラミング言語のひとつ From Wikipedia, the free encyclopedia

C言語(シーげんご、: C programming language)は、1972年AT&Tベル研究所デニス・リッチーが主体となって開発した汎用プログラミング言語である。英語圏では「C language」または単に「C」と呼ばれることが多い。日本でも文書や文脈によっては同様に「C」と呼ぶことがある。高水準言語であり、特に制御構文などにその特徴を持ちながら、ハードウェア寄りの記述も可能な低水準言語に近い特徴も併せ持つ。基幹系システムや、動作環境の資源制約が厳しい、あるいは実行速度性能が要求されるソフトウェアの開発に用いられることが多い。後発のC++JavaC#など、「C系」と呼ばれる派生言語の始祖でもある[注釈 1]

登場時期 1972年 (54年前) (1972).
最新リリース ISO/IEC 9899:2024/ 2024年10月31日 (21か月前) (2024-10-31)
概要 パラダイム, 登場時期 ...
閉じる

カテゴリ / テンプレート

ANSIISO、またJISにより言語仕様が標準規格化されている。

特徴

C言語には他のプログラミング言語と比較して、設計思想、言語仕様、実行環境、適用分野やエコシステム、安全性などの多岐に渡って特筆すべき特徴がある。

また、プログラマ人口が多く、アマチュアからプロ技術者まで幅広くプログラマのコミュニティが充実している。使用者の多さと幅広さから、C言語は正負の両面でプログラミング文化に様々な影響を及ぼしている。

設計思想と位置づけ

C言語は、ALGOLの影響を強く受け、主に構造化プログラミングパラダイムに対応した、手続き型の高水準言語(高級言語)である。構造化プログラミングにより、手順を入れ子構造で示して見通しの良い記述をすることができるため、機械語アセンブリ言語(アセンブラ)のような低水準言語(低級言語)と比較して、ソースコードの再利用性やメンテナンス性に優れており、目的に応じたプログラムの変更や拡張が容易となっている。

コンピュータの構成の影響を強く受けた文法を持ち、仕様自体は単純である。そのため、汎用性およびプログラムの自由度が高い一方で、後発言語と比較してプログラマが記述しなければならないことが多く、また低水準言語のようにコンピュータの構成に対する一定の知識を要求する。 また、ハードウェアをある程度抽象化しつつも、必要に応じてアセンブリ言語で行うような低水準の操作を行うことが可能である。ポインタ演算、ビットごとの論理演算シフト演算などの機能を持ち、ハードウェアの直接的な制御を必要とする処理を記述できる(システムプログラミング言語)。 そのため、「低水準な記述ができる高水準言語」のほか「高水準言語の顔をした低水準言語(高級アセンブラ[1]、汎用アセンブラ[2])」などと説明されることがあるが、型やオブジェクトなどの抽象は高水準言語の特徴であり、アセンブリ言語のようにC言語を理解することは困難である。

開発時期が古いこともあり、当時のコンピュータの性能などの制約から、実行速度や処理系の簡素化、移植性などが重視されている。 たとえば、C言語の仕様は詳細を規格で規定せず、選択肢を複数提示して処理系に選択を委ねている部分が数多く存在する(未規定動作)。これはハードウェアの特性に合わせて柔軟な実装を可能とするためであり、未規定動作には文書化された処理系定義動作もあれば、されていないものもある。そのため、C言語で書かれたソフトウェアには処理系に依存するものが多い。 コンパイル速度やメモリ使用の低減のため、コンパイラが1パスで処理を完結できる文法を採用している。

このような事情から、プログラムの安全性については優先度が低くなっている。リソースや性能要求の厳しい用途にも用いることができる一方、範囲外アクセスや境界値の逸脱など、言語規格で定義されない不正な操作を実行した際の挙動は未定義動作とされ、エラーの表示やプログラムの停止、安全なフォールバック処理などは保証されない。 パーソナルコンピュータなどの性能向上により、実行環境やプログラムの内容によっては、このような問題に対処しても無視できる程度の速度低下になることから、欠点として紹介されることがあるが、C言語は多くの環境で利用されており、実行速度や簡素な処理系が重要な分野も未だに存在する。

入出力動的メモリ確保を含めほとんどの機能が、C言語自身で書かれた標準ライブラリによって提供される。C言語は機種や環境に依存する部分を言語仕様から排除し、それらをライブラリへ分離することにより移植性が高く保たれている。これにより、C言語は様々な機種において普及することが可能となった[要出典]

適用分野とエコシステム

UNIX上のアプリケーションソフトウェアやUNIX自体、およびCコンパイラ自体を記述し、またそれらの移植性を高めるために開発されてきた経緯から、C言語は低水準から高水準まで広く記述可能な言語となっており、マイコン制御や機械制御など下位層から、ファームウェアオペレーティングシステム(OS)、デバイスドライバーの記述、シェルやコンパイラ、アプリケーションソフトウェアの開発のような上位層まで、商用・非商用を問わずあらゆる分野で利用される。

プログラムの実行に必要なリソースが他の高水準言語より少ない[注釈 2]ため、電化製品などの組み込みシステムでの利用が多い。組み込み向けのチップでは、アセンブリ言語以外ではC/C++しか対応するプログラミング言語が用意されないことがある。その場合、他のプログラミング言語はC/C++で書かれた処理系が存在すれば、コンパイルすることにより利用できることもあるが、メモリ制約などで動作しない可能性もある。

対応する機器の範囲も広く、パーソナルコンピュータワークステーション、自動車や家電の組み込みマイコンからスーパーコンピュータまで、C言語を使用できるハードウェアは多様である。そのため、C言語のコード資産が蓄積されている環境や分野は多岐に渡る。 また、プログラム作成やデバッグのための補助的なソフトウェア(プログラミングツール)が豊富であり、コンパイラやIDEなどの開発環境やライブラリがCPUやOSに付属している場合もあるほか、広く利用される環境においては無償で配布されている場合も珍しくない。

ANSI/ISOにより規格が標準化された後は言語仕様の変化が小さく安定していること、C言語のプログラマ人口やコード資産が多いこと、C++Objective-CからC言語のコードを直接利用できること、単純な仕様により他のプログラミング言語からC言語のコードを呼び出すためのバインディングを記述することが容易であることなどから、APIABIの外部仕様としてC言語のインターフェイスが選ばれることが多い。例えばOpenGLOpenCLのようなオープン規格は第一級言語としてC言語を採用している。

ただし、スクリプト言語やコマンドラインシェルを使えば手軽に実現できるような処理であれば、わざわざC言語で多くのコードを書き、効率を下げて安全性を損なう必要はないほか、一部のGUIアプリケーションフレームワークやAPI・ABIは、新しいプログラミングパラダイムに対応した後発言語からの利用を想定し、C言語からの利用が困難な設計となっていることもあるため、適切な言語を利用することが望ましい。

言語仕様と機能

改行文字にも空白にもトークンの区切りとしての意味しか持たせない「フリーフォーマット」という形式を採用している。

ブロック構造(複数の文をまとめた文。複合文)をサポートしており、式文や宣言文の区切りを終端記号 セミコロン ; によって、複合文の区切りを中括弧 { } によって表す。

C言語は構造化プログラミングのパラダイムに対応するため、ループや分岐の構文を持っており、原理的に無条件分岐(goto)を使用する必要はない(そして、実際に多くの場合は使用されない)。

C言語ではソースコード上の文字の大文字・小文字が区別される。

モジュール

C言語はスコープの概念を持ち、内部の変数を外部から用いることはできない。ブロック構造を単位とした局所スコープファイルを単位としたファイルスコープstatic[注釈 3])、プログラムの全体を範囲とする大域スコープがある。

また、ファイル単位でモジュール化を行うことができ、不透明ポインタ(opaque pointer)などによるカプセル化を行うことが可能であるほか、コンパイル可能であり、再利用可能な単位として扱うことができる。

プログラムは戻り値つきのサブルーチンに分離でき、C言語ではこれを関数と呼ぶ。これにより、再帰呼び出しが可能となる[注釈 4]。また、関数は複合文から成り立つため、関数ごとに独立したスコープを持つ変数(ローカル変数)が使用でき、データの流れが関数内で完結するのでデバッグが容易になる。

関数に引数として値を渡す際、仮引数実引数によって初期化される。C言語においては引数の初期化には代入と同じ規則が適用され、また代入は値の複製を意味するため、仮引数は実引数の複製となる(値渡し)。C++と異なり参照渡しは存在しないため、同様の効果を得るにはポインタを値渡しするポインタ渡し)。 C言語には配列を配列に代入する構文はないため、配列を引数として渡す場合は配列名のポインタへの代入と同じ規則が適用される。このため、後述の Array decay が発生することで、仮引数は配列の先頭要素を指すポインタによって初期化される。また、関数の仮引数における配列形式の(int arr[] のような)宣言は、ポインタ(int *arr)として扱われる。

配列と型

C言語は組み込みの整数型および浮動小数点数型のほか、構造体共用体、列挙体(列挙型)によるユーザー定義のデータ型や列挙定数などをサポートする。構造体および共用体はビットフィールドをサポートする。

ポインタは変更可能な左辺値になれるが、配列は変更可能な左辺値になれないほか、宣言の仕方や領域確保の仕組み、メモリに配置されたデータ、変数のサイズなど多くの点で配列とポインタは異なるものであり、一致しない。しかし、特定の式においては配列名はその配列の先頭要素を指すポインタとして評価される(Array decay)。そのため、配列をポインタに代入したり、配列名を基準にポインタ演算を行うことが可能である。格下げされない例としては、sizeof() 演算子や & 演算子などがある。

配列の要素へのアクセス(添字表記arr[2] のような)は、ポインタ演算と間接参照を合わせたもの(*(arr + 2))と同一であると定義されている(糖衣構文)ため、添字表記を用いてポインタの間接参照を行うことや、ポインタ演算と間接参照で配列の要素にアクセスすることが可能である。そのため、arr[2]2[arr] は同義となる。

C言語には文字列を格納するための特別な型が存在せず、ヌル文字'\0')を終端とするchar型の配列を利用する。char型は1バイト[注釈 5]の整数型[注釈 6]であり、これを文字型という。標準ライブラリにはこの文字型の配列を操作する関数が複数存在しており、比較やコピーなどを行うことができる。

プリプロセス

マクロ記述やコンパイル条件の指定などプリプロセッサ指令[注釈 7](preprocessing directive)があり、Cコンパイラはプリプロセッサ(preprocessor)を持つ。プリプロセッサはその名の通りコンパイルの前に実行され、プリプロセッサ指令に従って、あるいはコメントを削除するなどコンパイル前の準備処理(プリプロセス処理、前処理)を行う。コンパイラの機能として、プリプロセッサを通しただけの段階のソースコードを出力可能になっているものがあり、プリプロセス処理の結果を検査することで、設計者の意図とプリプロセス処理の結果のずれがないか確認できる。

ソースコードの記述に使う文字集合はANSI C(C89)およびISO/IEC 9899:1990(C90)ではASCIIを標準としている。他の文字集合でも書けるように、ISO 646でも利用可能な3文字で1つの記号を表記するトライグラフ、もう少し多くの文字が利用可能な環境向けにはよりわかりやすい、2文字で1つの記号を表記するダイグラフと呼ばれる記法を規定している。トライグラフの処理はプリプロセス処理よりも前に行われ、また文字列を機械的に置き換えるため問題が発生しやすく、C23で廃止された。ダイグラフはトークン化段階で識別されるため、問題が発生することはあまりない。

コンパイル仕様

C言語は最適化やベクトル化を妨げるポインタのエイリアシング[3]など、現代的なコンパイラによる最適化が難しい言語仕様を持ち、最適化を実行するとコンパイル速度が遅くなることがある。

歴史的な経緯から、変数の宣言において型指定がない場合はint型とみなし、関数戻り値の型指定がない場合はint型とみなす。また、ANSI C(C89)ではコンパイル時型検査の強化のために関数プロトタイプの機能が導入されたが、関数の宣言がない場合の戻り値はint型、引数は未知(任意)とみなす仕様であり、これらは使用より前に適切に宣言する必要がある。このような暗黙の型指定は、型安全性を損ない早期のバグ発見が困難であるため、ISO C(C99以降)では標準規格の文面から削除された。

GCCClangといったC99準拠のコンパイラは、C99モードであってもこのような暗黙の型指定についてC89互換の動作を残してはいるものの、非標準の動作であるため警告を出すようになっている。なお、関数宣言において () のように引数を省略すると引数を未知とする仕様は、C99・C11・C17ではいずれも残され、C23において削除された。

後継言語では完全なプロトタイプ宣言を必須とするか、あるいはプロトタイプ宣言自体を不要としているが、記述によっては先読みが必要になり得る。

外部連携

標準ライブラリの関数は、C言語の規格で規定された関数とPOSIXの規格で規定された関数、OSや特定のlibcが提供する関数などの整合性、棲み分けなどが流動的である。

多くの処理系がインラインアセンブラを搭載しているほか、アセンブラで出力したオブジェクトとのリンクが容易になっている。これにより速度が要求される部分だけをアセンブリ言語で記述する、といったことがよく行われる。アセンブラとのインタフェース#pragma asm などを用いて局所化を図る努力はあるが、コンパイラごとに定義されておりCPUが同一であっても移植性が低い場合がある。

実行環境

C言語ではOSを前提としたホスト環境と、割り込み制御のようなOSを前提としないフリースタンディング環境とがある。 ホスト環境では、プログラム開始直後に実行するプログラム要素を main という名前の関数として定義する[注釈 8]。C言語においては、プログラム中で再帰的にmain関数を呼ぶことも可能である(C++では不可能[4][5])。一方、フリースタンディング環境ではエントリーポイントと呼ばれるメモリアドレスに配置されたコードをプログラムの開始点とするが、それがmain関数である必要はない。

環境の違いは、動的メモリ確保などのシステムAPIの利用にも影響する。例えば、POSIX環境での動的メモリ確保はmallocおよびその類似関数にて提供される。一方、カーネルではメモリ確保の際にスレッドがブロックされるとカーネル内のデータが他のスレッドにより変更され、予期せぬ動作を起こす恐れがあることや、メモリ内容の初期化が必要かどうかによって割当先のページを選択することによりシステムの効率が上がることから、POSIXとは異なる独自のAPIが使用されることが多い。例えばLinuxカーネルの場合、スレッドブロックの回避はGFP_KERNELフラグとGFP_ATOMICフラグの使い分けで、メモリの初期化状態の制御はkmalloc関数(割り当てたメモリの内容は不定)とkzalloc関数(割り当てたメモリの内容はゼロクリア済)の使い分けによって実装されている[6]

安全性

C言語においては、変数の中でも最も頻繁に用いられる、自動変数(auto variable、静的でないローカル変数)は自動的には初期化されない。自動変数の自動とは、あくまで変数の領域の確保と解放が自動であるという意味である。初期化されていない変数を参照した場合にはその値は不定であるが、不定な値へのアクセスは未定義動作であるので、コンパイラ最適化の過程で関連する処理が全て消去されるなど、想定しない挙動に変換されることがある[7]。C99より前では変数宣言の仕様上の制限から、変数宣言の時点では初期化を行わず、後で代入等により値を入れて使用することが一般的であるため、誤って不定の値の変数を読み出すバグを発生させやすかった。後発言語では、明示的な初期化が記述されていない変数は不定値ではなく、その変数の型の既定値(ゼロあるいはゼロ相当の値)で初期化される仕様になっていることも多い。

配列の要素にアクセスする際に、添字の値が範囲内にあるかを検査しない。これは前述の通り添字表記がポインタへの格下げを伴うためであるが、これが要因となって固定長のバッファ領域をはみだしてデータの書き込みが行われてしまうバッファオーバーフロー(バッファオーバーラン)が発生することがある。範囲外のアクセスは、書き込みだけでなく読み取りの場合も未定義動作を引き起こす。標準ライブラリにはバッファオーバーフローや範囲外アクセスを考慮していない関数があり、かつ多用されがちなため、しばしば範囲外のメモリを読み書きすることで脆弱性の原因となる。C言語では動的メモリ確保による可変長配列の利用が可能だが、確保した領域の範囲外にアクセスしても自動的な伸長が行なわれるわけではなく、明示的に行う必要がある。後発言語では標準ライブラリや組み込み型によって可変長配列をサポートしたり、範囲外アクセス時には例外(実行時エラー)を送出したりするなどして、安全性を保証することが多い。

また、前述の通りC言語には文字列を格納するための特別な型が存在せず、ヌル文字を終端とするchar型の配列を利用し、標準ライブラリにはこれを操作する関数が複数存在している。前述の配列の場合同様、これらは実質的にメモリ領域へのポインタアクセスを行っており、確保されている領域の長さよりも長い文字列を書き込めてしまうために、バッファオーバーランの原因の1つとなっている。また、終端にヌル文字がない場合に停止することが保証されない関数もあり、安全性が低いとされている。こちらも後発言語では標準ライブラリや組み込み型によって文字列型をサポートし、安全に文字列処理を行えるように設計されることが多い。

ポインタはオペレーティングシステムやデバイスドライバーなどを記述する上では便利であるが、発見しづらく深刻なバッファオーバーフロー、ダングリングポインタ、解放後使用、二重解放などのメモリ安全性にかかわる様々なバグ、あるいは脆弱性の原因となる。Microsoftの製品群やGoogle Chromeにおける脆弱性の70%がメモリ安全性の問題であることが報告されており[8][9]、ホワイトハウスはメモリ安全性の観点からC/C++の廃止を推奨している[10]

MISRA CCERT Cというコーディング標準(コーディング規約)を定義して、危険な機能の使用や記述を禁止あるいは制限することで安全に利用するためのガイドラインが運用されている分野もある。これらの規約では、C言語規格で規定されている関数の妥当性について指摘し、いくつかの関数を利用しないように規定している場合がある。その他にも多くの規定があり、特にプログラミングミスが人命に直結する自動車分野などでC言語を利用するには、このような制約が重要とされる。

コード例

Hello worldプログラム

C言語のHello worldプログラムは、ホスト環境を前提とするか、フリースタンディング環境を前提とするかで、方向性が異なる。ホスト環境を前提とする場合には、標準入出力の利用により、動作をすぐに確かめることができる。以下では、標準Cライブラリヘッダstdio.hにて宣言されている、printf関数を利用したものを例示する。

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

上記サンプルソース中の「\n」は、エスケープ文字\によるエスケープシーケンスのひとつであり、改行(ラインフィード)を表す。

main関数は標準的なプログラムエントリーポイントであり、プログラムを開始すると、ランタイムライブラリによるスタートアップ処理が実行された後にこのmain関数が呼ばれる。引数のないバージョン(void)と、コマンドライン引数をポインタ配列として受け取るバージョン(int argc, char* argv[])どちらを使ってもよい[11]

return 0;main関数の戻り値としてint型の値0を返している。C99以降では、main関数の戻り値のデータ型がintと互換性があるときは暗黙的に0を返す[11]。従って、C99以降ではreturn 0;を省略しても同じ結果になる。

なお、printf関数は書式文字列とそれに対応する可変長引数を受け取り、書式化された文字列として表示できる高機能な標準出力関数であり、難解な関数と言われている[12]

主な制御構造

主な標準ライブラリ関数

歴史

誕生

C言語は、AT&Tベル研究所のケン・トンプソンが開発したB言語の改良として誕生した(#外部リンクの「The Development of the C Language」参照)。

1972年、トンプソンとUNIXの開発を行っていたデニス・リッチーはB言語を改良し、実行可能な機械語を直接生成するC言語のコンパイラを開発した[13]。後に、UNIXは大部分をC言語によって書き換えられ、C言語のコンパイラ自体も移植性の高い実装のPortable C Compilerに置き換わったこともあり、UNIX上のプログラムはその後にC言語を広く利用するようになった。

ちなみに、「UNIXを開発するためにC言語が作り出された」と言われることがあるが、「The Development of the C Language」によると、これは正しくなく、経緯は以下の通りである。C言語は、当初はあくまでもOS上で動くユーティリティを作成する目的で作り出されたものであり、OSのカーネルを記述するために使われるようになるのは後の展開である。

  • UNIXの開発当初、Multicsプロジェクトが目指していた高級言語によるOSの開発という目標は見送られた。
  • アセンブリ言語でUNIXが作成されると、OS上で動くユーティリティを作成するためのプログラミング言語が必要とされた。
  • ケン・トンプソンは、当初Fortranコンパイラを作ろうとしたが、途中で放棄し、新しい言語であるB言語を作成した。
  • B言語はインタプリタ言語であったため動作が遅く、B言語でユーティリティを作ることはあまりなかった。
開発者達は、コンパイラなどのユーティリティを「システムプログラム」と呼んでいたが、それらの作成に使われる「システムプログラミング言語」は、OSのカーネルを作成するための言語という意味ではない[14]
  • B言語の欠点を解消するため、1971年に改良作業を開始した。
  • 1972年にC言語のコンパイラができあがり、UNIXバージョン2において、いくつかのユーティリティを作成するために使用された。

UNIX環境とC言語

アセンブリ言語との親和性が高いために、ハードウェアに密着したコーディングがやりやすかったこと、言語仕様が小さいためコンパイラの開発が楽だったこと、小さな資源で動く実行プログラムを作りやすかったこと、UNIX環境での実績があり、後述のK&Rといった解説文書が存在していたことなど、さまざまな要因からC言語は業務開発や情報処理研究での利用者を増やしていった。特にメーカー間でオペレーティングシステムやCPUなどのアーキテクチャが違うUNIX環境では再移植の必要性がしばしば生じて、プログラムをC言語で書いてソースレベル互換[15]を確保することが標準となった。

C言語誕生時の環境と他言語との比較

C言語の開発当初に使われた入力端末はASR-37英語版であったことが知られている[14]。ASR-37は1967年制定の旧ASCII ISO R646-7bitにもとづいており、「{」および「}」の入力を行うことができたが、当時は一般的に使われていた入力端末ではなかった。 当時PDP-11の入力端末として広く使われていたのはASR-33であるが、これは1963年制定の旧ASCIIであるASA X3.4に準拠しており、「{」や「}」の入力を行うことはできなかった[16]

このことは、ブロック構造に「{」や「}」を用いるC言語(さらに元をたどればB言語)は、当時の一般的な環境では使用不可能であったことを示している。これは、C言語はその誕生当初にあっては一般に広く使われることを想定しておらず、ベル研究所内部で使われることを一義的に考えた言語であったという側面の表れである。

これに対し、PascalBASIC等の当初から広く使われることを想定した言語では、ブロック構造に記号を用いずにbeginendをトークンとして用いることや、コメント行を表す際に開始トークンとしてREMという文字列を用いることなど、記号入力に制約がある多くの入力端末に対応できるように配慮されていた。この頃の他の言語やOSで大文字と小文字の区別をしないものが多いのも、当時は大文字しか入力できない環境も少なくなかったことの表れである。

このような事情のため、C言語が普及するのは、ASCII対応端末が一般化した1980年代に入ってからである。

現在、ブロック構造の書式等で、{...}形式のC言語と、begin...end等を使用する他の言語との比較において優劣を論じられることがあるが、開発時の環境等をふまえずに現時点での利便性のみで論じるのは適切ではない場合があることに留意が必要である。

PCとC言語

1980年代に普及し始めたパーソナルコンピュータ(PC)は当初、8ビットCPUでROM-BASICを搭載していたものも多く、BASICが普及していたが、1980年代後半以降、16ビットCPUを採用しメモリも増えた(ROM-BASIC非搭載の)PCが主流になりだすと、Turbo CやQuick Cといった2万円程度の比較的安価なコンパイラが存在したこともあり、ユーザーが急増した。8ビットや8086系のPCへの移植は、ポインタなどに制限や拡張を加えることで解決していた。

現在のC言語

1990年代中盤には、最初に学ぶプログラミング言語としても主流となった。また、同時期にはゲーム専用機(ゲームコンソール)の性能向上とプログラムの大規模化、マルチプラットフォーム展開を受け、メインの開発言語がアセンブリ言語からC言語に移行した。

1990年代後半 - 2000年代以降は、PCのさらなる性能向上と普及、GUI環境やオブジェクト指向の普及、インターネットおよびウェブブラウザの普及、スマートフォンの普及に伴い、より高水準で開発効率の高い言語やフレームワークを求める開発者が増えたことにより、C++Visual BasicJavaC#Objective-CPHPJavaScriptなどが台頭してきた。広く利用されるプログラミング言語の数は増加傾向にあり、相対的にC言語が使われる場面は減りつつある。特にアプリケーションソフトウェアなどの上位層の開発には、C言語よりも記述性に優れるC++、Java、C#などC言語派生の後発言語が利用されることが多くなっている。資源制約の厳しかったゲーム開発においても、ハードウェアの性能向上やミドルウェアの普及により、C++やC#などが使われる場面が増えている。速度性能や省メモリが特に重視されるシステムプログラミングに関しても、伝統的にC/C++の独壇場だったが、新規コードではより安全性の高いRustを導入する事例が現れている[17][18]

しかし、C言語は比較的移植性に優れた言語であり、個人開発/業務用開発/学術研究開発やプロプライエタリ/オープンソースを問わず、オペレーティングシステムやデバイスドライバーなどの下位層、クロスプラットフォームAPIの外部仕様、C++やJavaなどの高水準言語の処理系および実行環境の実装が困難な小規模の組み込みシステムなどを中心に、2021年現在でも幅広く利用されている。

プログラミング入門者にとってはPython、JavaScript、KotlinSwiftなどのように、煩雑なメモリ管理が不要で、ハードウェアがより抽象化されており、インタラクティブな対話環境(インタプリタREPL)が利用でき、危険な機能を制限した高水準言語の方が取っ付きやすいが、コンピュータの動作原理やハードウェア仕様を理解するには、C言語のような適度な抽象度の言語を用いた方がわかりやすい。

規格

K&R

米国国家規格協会(ANSI)による標準化が行われるまで、1978年出版のデニス・リッチーブライアン・カーニハンの共著『The C Programming Language』が実質的なC言語の標準として参照されてきた。この書籍は、著者らのイニシャルを取って「K&R」とも呼ばれている。C言語は発展可能な言語で、K&Rの記述も発展の可能性のある部分は厳密な記述をしておらず、曖昧な部分が存在していた。そのためC言語が普及するとともに、互換性のない処理系が数多く誕生した。

C89/C90

そこで、ISO/IEC JTC1とANSIは協同でC言語の規格の標準化を進め、1989年12月にANSIがANSI X3.159-1989, American National Standard for Information Systems -Programming Language-Cを、1990年12月にISOがINTERNATIONAL STANDARD ISO/IEC 9899:1990(E) Programming Languages-Cを発行した。ISO/IEC規格のほうが章立てを追加しており、その後ANSIもISO/IEC規格にならって章立てを追加した。それぞれC89(ANSI C89)およびISO/IEC C90という通称で呼ぶことがある。

日本では、これを翻訳したものを『JIS X 3010-1993 プログラム言語C』として、1993年10月に制定した。

最大の特徴は、C++と同様の関数プロトタイプ[注釈 9]を導入して引数の型チェックを強化したことと、voidenumなどの新しい型を導入したことである。一方、「処理系に依存するものとする」に留めた部分も幾つかある(int型のビット幅、char型の符号、ビットフィールドエンディアン、シフト演算の挙動、構造体などへのパディング等)。

規格では以下の3種類の自由を認めている部分がいくつかある[19]

  • 規格で定義しないことを決めている「未定義(undefined)」
  • 規格で選択肢を定義したもののどれにするかを決めておらず、処理系が選択する必要があるが、文書化の必要はない「未規定(unspecified)」
  • 処理系ごとに決めて文書化する必要のある「処理系定義(implementation-defined)」

これにより、プラットフォームやプロセッサアーキテクチャとの相性による有利不利が生じないような仕様になっている。

8ビット/16ビット/32ビットなど、レジスタ幅(ワードサイズ)の異なるプロセッサ(CPU)に対応・最適化できるようにするため、組み込み型の情報量(大きさ)や内部表現にも処理系の自由を認めている。型のバイト数はsizeof演算子で取得し、各型の最小値・最大値はlimits.hで定義されているマクロ定数で参照することとしている。1バイトあたりのビット数は規格で定められてはおらず、多くの処理系ではchar型は8ビットである(オクテット)が、C言語の仕様上は8ビットとは限らない。1バイトのビット数はCHAR_BITマクロ定数で取得でき、char型が1バイトである(sizeof(char) == 1)ことは常に保証される。また、その他の整数型については、sizeof(int) >= 2sizeof(int) >= sizeof(short)sizeof(long) >= sizeof(int)、という大小関係のみ定められている(符号無しも同様)。多くの処理系ではintlongのサイズはCPUのレジスタ幅などによって決められ、short型のサイズは2バイト(16ビット)である。int型、short型、long型で符号を明示しない場合はsignedを付けた符号付き型として扱われる。しかし、文字型であるchar型に関しては、signed(符号付き)にするか、それともunsigned(符号無し)にするかは処理系依存である。char型、signed char型、unsigned char型はいずれもサイズは1バイトだが、それぞれ異なる型として扱われる。

規格上には、BCPLやC++形式の1行コメント(//…)は無いが、オプションで対応した処理系も多く、GCCやClangはGNU拡張-std=gnu89でサポートしている。

GCCやClangでは、-std=c89(または-ansiもしくは-std=c90)をつけることにより、GNU拡張を使わないC89規格に準拠したコンパイルを行うことができる[注釈 10]。加えて、-pedanticをつければ診断結果が出る。商用のコンパイラではWatcom Cコンパイラが規格適合の比率が高いと言われていた。現在Open Watcomとして公開している。

C89には、下記の追加の訂正と追加を行った。

  • ISO/IEC 9899/COR1:1994
  • ISO/IEC 9899/AMD1:1995 - 英語圏での利用を想定して制定したC89に対して、国際化のためワイド文字版ライブラリを追加したAmendment1が1995年に発行された。
  • ISO/IEC 9899/COR2:1996

C99

1999年12月1日に、ISO/IEC JTC1 SC22 WG14 で規格の改訂を行い、C++の機能のいくつかを取り込むことを含め機能を拡張し、ISO/IEC 9899:1999(E) Programming Language--C(Second Edition)を制定した。この版のC言語の規格を、通称としてC99と呼ぶ。

日本では、日本産業規格 JIS X 3010:2003「プログラム言語C」がある。

主な追加機能:

  • 変数宣言がブロックの先頭でなくても良くなった。
  • ブール代数を扱うための_Bool型が予約語に追加され、標準ライブラリとしてstdbool.hを追加した。
  • 複素数を扱うための_Complex型や_Imaginary型を予約語に追加し、標準ライブラリとして、complex.hを追加した。
  • 少なくとも64ビットの整数値を保持できる long long int型の追加。
  • オプションとして、固定幅かつ内部表現の規定された整数型の標準化(stdint.h)。
  • //による1行コメント。
  • インライン関数(inlineキーワード)。
  • 可変長配列alloca関数の代替)[20]

C99は下記の訂正がある。

  • ISO/IEC 9899:1999 Cor. 1:2001(E)
  • ISO/IEC 9899:1999 Cor. 2:2004(E)
  • ISO/IEC 9899:1999 Cor. 3:2007(E)

C11

2011年12月8日ISO/IEC 9899:2011(通称・C11)として改訂された。

C11はUnicode文字列(UTF-32UTF-16UTF-8の各符号化方式)に標準で対応している。そのほか、type-generic式、C++と同様の無名構造体・無名共用体、排他的アクセスによるファイルオープン方法、quick_exitなどのいくつかの標準関数などを追加した。

また、_Noreturn関数指示子を追加した。_Noreturnは従来処理系ごとに独自に付加していた属性情報(たとえばgccでは__attribute__((__noreturn__)))を標準化したもので、「呼び出し元に戻ることがない」という特殊な関数についてその特性を示すためにある。return文を持たない関数という意味ではなく(規格ではreturn文を持たなくとも、関数の最後の文の実行が終われば制御は呼び出し元に戻る)、_exitexecveを実行したり、例外、longjmpによる大域ジャンプ[注釈 11]などのために、制御が呼び出し元に戻らないことを明示するためにある。そのような関数は、スタックに戻りアドレスを積む通常の呼び出しではなく、スタックを消費しないジャンプによって実行できる。

C11規格では一部の機能を省略可能とした。即ちコンパイラがC11に合致していても、一部機能は提供しないことがある。コンパイラがどの機能を提供しているかは、テスト用のマクロで判別できる。アラインメント機能や_Atomic型、C言語ネイティブの原始的なスレッド機能などが、C11では省略可能な機能として追加された。また、複素数型と可変長配列はC99では必須機能であったが、C11では省略可能である。

gets関数が廃止された。

C17

2018年7月5日にISO/IEC 9899:2018(通称・C17またはC18)として改訂された。仕様の欠陥修正がメインのマイナーアップデートである[21]

C23

2024年10月31日にISO/IEC 9899:2024(通称・C23)として改訂された[22]

C++由来のキーワードやリテラル表現が多く取り入れられ(ただし、厳密な仕様は異なる場合がある)、NULLマクロ(通常(void*)0もしくは0である)に代わる型安全なヌルポインタ定数nullptrとその型nullptr_t型、変数宣言時に右辺から型を推論するautoやコンパイル時定数を宣言するconstexprなどが追加されたほか、2進数リテラルや桁区切り文字、空の波括弧{}による初期化、UTF-8で符号化された文字リテラルを表す接頭辞u8などがサポートされた。

C23ではプリプロセッサを強化し、条件付きディレクティブの#elifdef#elifndef、バイナリリソースを埋め込むための#embed、診断用の#warning、ヘッダの存在を確認する__has_include、属性の存在を確認する__has_c_attribute、バイナリリソースの存在を確認する__has_embedなど多くのディレクティブを追加した。

その他、ビット操作関数が多数追加された新しいヘッダー<stdbit.h>や、任意ビット長整数の_BitInt(N)unsigned _BitInt(N)とその最大ビット幅を表すBITINT_MAXWIDTHマクロ、式や型から型を取得するtypeoftypeof_unqual演算子、[[deprecated]][[nodiscard]][[maybe_unused]][[fallthrough]]など属性構文、最適化で削除されないメモリ消去関数memset_explicit()、POSIX関数であるstrdup()strndup()memccpy()など様々な機能が追加されている。enumの基礎となる型の指定も可能となった。

また、既存機能の整理や非推奨化が進み、K&R形式の関数定義やトライグラフ(??=など)が廃止され、関数定義で(void)のほか()でも引数なしとして扱われるようになり、truefalse<stdbool.h>をインクルードせずに標準キーワードとして利用できるほか、_Alignas_Alignof_Bool_Static_assert_Thread_localがそれぞれalignasalignofboolstatic_assertthread_localで扱えるようになり、符号付き整数の内部表現として2の補数表現が義務化された。

主なC言語処理系

大抵の処理系はC言語とC++双方をサポートしている。C言語とC++の共通部分を明確にし、2つの言語の違いに矛盾が生じないようにすることが課題になっている。

Linux・UNIX・Windows用

GNUコンパイラコレクション(GCC)
C/C++以外の言語もサポートし、多数のCPUやオペレーティングシステムに対応、組み込み向けも含む多様な開発に広く使われるオープンソースのコンパイラ。GNU C拡張と呼ばれる拡張を持ち、多くの機能が追加されている。
GCC 4.5でC99を[23]、GCC 4.9でC11を[24]、GCC 15でC23を[25]実質的に完全サポートした。
Clang
LLVMをバックエンドとして用いるオープンソースのC/C++・Objective-Cコンパイラ。多くのGNU C拡張を取り込んでおり、GCCほどではないが多数のCPUやオペレーティングシステムに対応する。
Microsoft Visual C++(MSVC)
Windows系プラットフォーム用のC/C++コンパイラ。ANSI C準拠(バージョン2013にてC99ライブラリをほぼ実装したが、言語機能など規格自体はサポートされていない)。x86・x64が主だが、Xbox 360Windows CE等向けにPowerPCARMMIPSItanium等に対応した版もある。前身としてMS-DOS・Windows用のMicrosoft C Compilerがある。またその廉価版としてQuick Cがあった[26]
C++ Builder
Windows/macOS/iOS/Android対応のC/C++コンパイラBCCを含む、RADツール。以前はWindowsおよびx86のみがメインターゲットだったが、Clang/LLVMをベースに再設計され、多数のプラットフォームやアーキテクチャをサポートするようになった[27]。前身はDOS/Windows用のBorland C/C++。さらに前身としてTurbo C/C++がある。
Intel C++ Compiler(ICL/ICC)
インテル製のIA-32x86)およびIntel 64x64)用のC/C++コンパイラ。Windows/Linux/macOS/Android向けがある。gcc互換。
バージョン11.1まではIA-64Itanium)をサポートするが、バージョン12.0以降ではサポートされない[28]
C99[29]とC11[30]の対応リストが公開されている。バージョン18.0でC11にほぼ対応している。
Open Watcom C/C++
Windows・Linux・OS/2・MS-DOS・DOSエクステンダを対象とするx86用C言語・C++コンパイラ。商用だったWatcom C/C++がオープンソース化したもの。
Portable C Compiler
gccが普及する以前のUNIXにおける標準的C言語コンパイラ。現在はオープンソース
Digital Mars C/C++
Windows・MS-DOS・DOSエクステンダを対象とするx86用のC言語・C++コンパイラ。無料版もある。ウォルター・ブライト作でDatalight C、Zorland C、Zortech C/C++、Symantec C/C++と変遷している。

組み込み用、8ビット・16ビット・32ビット・64ビットCPU用(クロスコンパイラ)

Green Hills Software C/C++英語版
組み込み向けのC言語・C++コンパイラ。 Windows用・Solaris用・Linux用があり、HP/UX用がver4ではあった。
CodeWarrior C/C++
組み込み向けやゲーム機開発向けのC言語・C++コンパイラ。Classic Mac OS用として発祥、かってはWindows用・BeOS用・Palm用もあった。
ARM C/C++
ARM CPU用C言語・C++コンパイラ。
IAR C/C++
新旧の組み込み向けCPU各種を広くカバーしていた。後に統合開発環境EW・SWに移行。使用には付属のドングルが必要だった。ARM CPU用C言語・C++コンパイラが著名。ARMをコアにした各社のCPUに対応している。
High C
元はx86向けでPC/AT互換機用だが80386のネイティブモードに対応したためFM TOWNSでも標準開発環境、「High C 386」として使用された。後に各社RISC向けとなる。
BDS-C
CP/M(8080・Z80)用のサブセット(整数のみ)のK&R系のC言語コンパイラ。現在はパブリックドメインソフトウェア
Hitech-C
Z80PICなど。
Lattice C
1980年代に、日本で高い普及率を見せたコンパイラ。解説書も多く出版されていた。日本での発売はライフボート。初期版はマイクロソフトCコンパイラ1.0として発売された。商用利用のできない個人向けの「personal」版も販売されており、これの価格は19,800円であった[31][32]
LSI C
8080Z80用のLSI C-80(セルフ版・クロス版。現在はクロス版のみ)と、8086用のLSI C-86がある。8086では機能限定(スモールモデルのプログラムしか開発できず、デバッガがない)の「試食版」がフリーソフトで公開され、広く使われた。
micro-C
8ビット・マイクロプロセッサMC6809用C言語サブセット・コンパイラ[33]
Small-C
元は8080向けの小型のC言語コンパイラだが派生版のクロスコンパイラとしてcc65(MOS6502用)や、z88dkなどがある。
SDCC
各種8ビット・マイクロプロセッサ向けのフリーソフトウェア(GPL)のC言語クロスコンパイラ。

関連する主なプログラミング言語

先祖

ALGOL
ヨーロッパ生まれのアルゴリズム記述言語。PascalやC言語などに影響を与えたとされる。
BCPL
MULTICSで作成された高級言語。
B言語
初期のUNIXで作成されたインタプリタ方式の高級言語。BCPLを元に作られ、Cの原型となった。

拡張・サブセット

C++
C言語を拡張してクラスベースオブジェクト指向を追加した言語。Simulaの影響を強く受けている。当初はC言語のスーパーセットだったが、現在は細かい部分において非互換仕様が増えており、C++の機能をC言語が逆輸入することも多い。RAIIを利用することでメモリ解放を自動化することもできる。
Objective-C
C言語を拡張してオブジェクト指向化したもの。C言語に Smalltalk のオブジェクトシステムを取り付けたような設計で、互換性は保たれている。C言語からの拡張部分がC++と干渉しないため、C++と混在した記述が可能。
Cg
C言語をGPU上での3次元コンピュータグラフィックス処理用に特化させたもの(シェーダー言語、シェーディング言語)。NVIDIAによって開発された。
SystemC
ハードウェア記述言語向けに拡張したもの。書式はC++。IEEE 1666-2005。ISO 8866:1991。
Impulse C英語版
ハードウェア記述言語向けに拡張したもの。書式はC。
Unified Parallel C
並列計算向けにC99を拡張して作られた言語。
Cyclone英語版
C言語と高い互換性を保ちつつ、メモリ安全性を高めた実装。ポインタの扱いを厳密にするためファットポインタや非NULLポインタが追加され、ダングリングポインタの防止やポインタ演算の制限を加えた方言である。その他リージョンベースメモリ管理システム、正規表現、タグ付共用体などが追加されている。開発終了。

影響を与えた言語

Rust
C/C++と同等の実行性能を持ち、その代替となることを目指したシステムプログラミング言語。言語仕様として所有権や借用といった概念を導入、RAIIを言語レベルで強制しコンパイル時に検証することで、ガベージコレクションを使わずに手動でのメモリ解放を不要としている。コード構造による安全なメモリ管理を行える反面、学習難易度が高いことでも知られる。
Java
C++よりも基礎的な文法レベルでクラスベースのオブジェクト指向を強制した言語。ダングリングポインタなどの危険性が高いポインタのような、ローレベルな要素を構文から排除しており、またJava仮想マシン(JVM)上で動作することでプラットフォーム非依存を実現している。
C#
マイクロソフト.NET構想に向けて開発した言語。構文はC/C++の記法を踏襲しており、同じくC/C++の影響を受けたJavaとも多くの類似点を持つ。一方で、言語機能やコンポーネント指向の設計思想においては、Delphiの影響を強く受けている。

その他にも、OpenGLシェーダー言語であるGLSLDirectXDirect3D)シェーダー言語であるHLSLOpenCLカーネル記述言語であるOpenCL-Cなど、C言語の文法的特徴を取り入れた派生言語やDSLが多数存在する。

脚注

参考文献

外部リンク

Related Articles

Timelines

Top Qs

Fact Checks