C言語とは、プログラミング言語の一つである。単にCとも呼ばれる。
概要
AT&T社のベル研究所でB言語の後継として、UNIXの移植性を高めるために作られた。
Bの次はアルファベットでCなのでCと命名されたとも、B言語の元となったBCPLの2番目の文字を採って命名されたとも言われる。
元々UNIXを記述する目的で内輪向けに作られた言語であるため、安全性はあまり考慮されていない。配列の添字チェックが無かったり、ポインタが無効でもアクセスしようとしたり、整数とポインタを自由にキャストできたり。やりたい放題。
しかし、記述の制約がそれまでの言語よりも少なく、より簡易的な記述が可能になったことや、GOTO文を排除した構造化プログラミングが容易に行えることから、1980年代より普及が進んでいった。
その後、C++、Java、C#といったプログラミング言語、Perl、JavaScript、PHPなどのスクリプティング言語の仕様に大きな影響を与えた。
C言語に関する規格
C言語はプログラミング言語の中でも比較的歴史が長く、何度か規格が改定されてきた。
K&R
K&Rとは、カーニハンとリッチーの共著『The C Programming Language』(邦題:プログラミング言語C)のこと。転じて、標準化以前のC言語を指す。K&Rは解説書であり規格書ではないため曖昧な記述も多く、これを元に作られたコンパイラ同士の非互換性を生むこととなった。
プログラミング言語を学ぶ人が一番最初に出会う事の多いプログラム、「Hello, World!」はこの本に由来する。
C89
ANSIが1989年に策定した規格。ANSI Cとも呼ばれる。これにより、初めてC言語の標準化が行われた。
C90
ISOがISO/IEC 9899として1990年に策定した規格。内容はC89と同一。
C95
ISOが1995年に策定した規格。C90に国際化のためワイド文字列関連のライブラリが追補として追加された。
C99
ISOが1999年に策定した規格。複素数や可変長配列など、様々な機能が追加された。
現在、一部を除いて一般的に用いられている規格。
C11
ISOが2011年に策定した規格。スレッドやUnicode対応など、またまた様々な機能が追加された。それと、gets関数が削除された。
C17(C18とも)
ISOが2018年に策定した規格。C11のマイナーアップデート。
C23
ISOが2024年に策定した規格。C++との互換性がより高まるような変更が多く導入されている。
こんなところで使われているよ
- ニコニコ大百科の自動リンク機能
- 2ちゃんねるのread.cgi
- Unix Kernel (Solaris, FreeBSD, NetBSD, OpenBSD...)
- Linux Kernel
- 組み込み開発(Arduino 開発環境など)
- Rubyインタプリタ
- その他ありとあらゆる分野
主なコンパイラ
- cl (Microsoft Visual C++のコンパイラ。Windowsのデファクトスタンダード。C99/C11未対応)
- gcc (GNU C Compiler。OSSのデファクトスタンダード。 C99を完全にはサポートしていない)
- clang (llvmバックエンドのコンパイラ。今後Mac OS XやFreeBSDの標準となる予定。)
- icc(Intel C Compiler。非Windows版は非商用に限り無料。)
- x86 Open64 Compiler Suite(AMD製コンパイラ。オープンソース。)
サンプルコード
Hello, World!
Cを学ぶ上で避けては通れないコード。
#include <stdio.h>
int main(void) {
printf("Hello, World!");
return 0;
}
よくある勘違い
void main
と定義するか、
のような同等の定義か、処理系依存の定義をしなければならない。(C99 5.1.2.2.1)
よって、
は、C言語の規格上定義されていない。
また、return せずに main 関数の最後まで到達した場合、main 関数は 0 を返したことになる。int と互換性の無い型の値を返した場合には不定となる。(C99 5.1.2.2.3)
よく気付かれるので、C言語の解説動画などでvoid main(void)
にすると荒れる原因となる。
絶対に使ってはいけない関数
諸ヘッダファイルに定義はされているもののその関数自体に重大なバグを抱えている関数がC言語には存在する。C言語プログラミングを学ぶ人のために、簡単にまとめておく(適宜追加してください)。
char *gets(char *s)
この関数は、標準ヘッダファイルstdio.hに宣言されており、 標準入力(stdin,普通はキーボード入力が該当)から改行文字かEOFまでの一行を受け取り、引数となるcharポインタ型の変数が指すバッファに格納する(末尾の改行文字'\n'やEOFは'\0'に置き換えられる)。
問題点は、例えば以下のような状況でバッファオーバーランを起こし、変な動作をする点である。
例えば、最初に変数として「char s[200];」を宣言し、「gets(s);」でキーボードから文字を入力することを考える。ここで、入力が改行記号を除いた199バイトより大きい場合、その余剰分のデータが分け与えられたメモリ領域とは別のところを書き込んでしまう。AAにするとこんな感じである(正確に言うと違うが、簡単に述べている)。
↓あるデータ ↓s[200] ↓あるデータ
…0010001000][000000…0000][100010010010…
↓199バイトより大きい文字列をgetsで取得
↓あるデータ ↓s[200] ↓あるデータ
…0010001000][101110…1011][011010110010…
この右側の下線をつけたところが変に書き込まれたされたところである。ここに、たまたま別ウィンドウで開いてたお宝画像のデータがあったとしたら…?そのデータは壊れて変な画像になったり、開けなくなってゴミと化すかもしれない。また、ここにOSの重要なプログラムが入っていたとしたら…?PCの動作が不安定になるのは明らかである。
ここではデータ破壊の可能性について論じたが、これがセキュリティホールの原因ではない。
たぶんs[200]の後には何らかの戻りアドレスが入っている確率が高い。そこで、sに「200バイトのダミーデータ」+「ニセの戻りアドレス」+「侵入用プログラム」と書くと、プログラムの送り込みと起動が1回の動作で実行できることになる。
対策は、fgets(s,sizeof(s),stdin)を使うこと。これも指定された数以上に入力があったらプログラムの動作がおかしくなる場合があるが、少なくとも大事なデータを書き換えたり、こんな簡単にシステムに侵入されることはない。
getsはあまりにダメダメすぎる存在だったので、最新の標準規格C11で削除された。
関連動画
関連リンク
関連項目
親記事
子記事
- なし
兄弟記事
- 12
- 0pt