C言語規格の変遷とコンパイラのサポート

ISOでC言語が初めて規格化されたのは1990年であり、西暦の下二桁を取ってこの規格は一般にC90と言われる。その後、C言語規格には2度大きな改良が行われた。それが1999年のC99と2011年のC11である。つまり、細かな補正などを除けば、2017年現在、C言語の規格には、C90、C99、C11の3つがある。1

では最新のC11のみを理解しておけば良いかというと、残念ながらそうではない。現場で現在よく利用されているのは、むしろ、C90とC99である。その理由の1つは、新たな規格が出来てからコンパイラがそれを一通りサポートするまでには、年単位で時間が必要だからである。例えば、GCCがC11を一通りサポートしたのは2014年のGCC 4.9からである。Microsoft Visual Studio 2015はC11どころかC99のサポートすら一部に留まっている。

規格と移植性

では、なぜC言語の規格の知識を持つべきなのだろうか。

第一に、そもそもの話として、C言語としてどのような文法や処理結果が正しいのか、また、どのような機能や型、関数を標準としてサポートしているのかを規定しているのがC言語規格だからである。例えば、規格の間には違いがあるため、どの規格についてなのかを不明にしたまま話を続けると、ときには混乱の要因になる。また、情報源の信頼性としても、言及している規格が明確にされていない情報には要注意である。特に、1999年以降に公表された書籍や、雑誌記事、ブログ記事、はたまた、社内文書において、C99も取り扱っているのか、それとも、C90の範囲に限っているのかを言及していなければ、残念ながらそれらは良い情報源とは言えない。なぜなら、C90とC99の話の切り分けが、読者の仕事になるからだ。確かな情報とするためには、結局は別の情報源を参照することになってしまう。 他として、規格の規定が重要なのは、コンパイラが規格に沿って作られているということもある。まともなコンパイラはサポートしている規格を明示しているものだ。もし複数の規格をサポートしている場合には、規格の切り替え方法も明示されているのが普通である。規格に沿ってコンパイラはソースコードを実行ファイルへと変換するのだから、複雑な現象に遭遇して困ったとき(残念ながら大抵はやっかいなバグ取りをしているときだろうが)、その現象を理解するのに最終的に信頼すべきは規格書である。(例えば、プロトタイプ宣言が無いと規格上どうなるかは先ほど説明した通りである。)

C言語規格の知識を持つべき第二の理由は、移植性を確保するためである。分かりやすいのは、CPUやOSに応じて複数のコンパイラを使うときだ。使うコンパイラの1つがC90までの対応ならば、当然C99で追加された関数をソースコードに用いることは単純には出来ない。また、あるコンパイラ独自の拡張も避ける必要がある。結局の所、どの規格のどの範囲が利用可能なのかは把握しておく必要がある。 また、規格書には処理系によって結果が異なる可能性がある動作が列挙されている。例えば、マイナスの値に対する余りの計算結果がそうである。他にも、ゼロ除算のようなエラーがあった後どうなるか分からないというのも規格書に書かれていることである。そういったエラーによって、プログラムが停止するのか、停止しないなら計算結果の値がどうなるかは、処理系によって違っても何ら不思議はない。というよりも、規格書には違っていて良いと書かれているのである。 加えて言っておくと、1つのコンパイラしか使っていないからといって、とりあえずコンパイルが通って動いているからいいやという態度は、抱え込む爆弾を日に日に増やすようなものである。コンパイラやOSの古くなったバージョンは、いつしかサポートが打ち切られる。CPUやマイコンといったハードウェアにも、生産終了の日がいつかはやってくる。そうして、新たな環境向けにビルドが必要になったとき、C言語規格を全く無視して書かれた移植性のないソースコードは爆発するのである。

未来を見通す: 廃止された関数gets

現在利用している環境がC90だったとしても、C99やC11で何が変化するのかを知っておくのは良いことだ。なぜなら、開発ツールや開発内容の変化のため、新しい規格の利用が必要となることは、長期的に見れば十分起こりうるからだ。

まず、新規格で廃止されたり、オプションとなったりしたものは、そうなる以前の規格でも利用は避けておこう。具体的には、gets関数がC11で廃止されている。理由は、致命的なセキュリティの問題(バッファオーバーラン)なので、規格の話を別にしてもgets関数は使用すべきでない。また、C99で導入された可変長配列と複素数関連の機能が、C11ではオプションになっている。つまり、これらの機能をサポートしないC11準拠コンパイラの存在が認められている。よって、これらの機能は安易に利用しない方が良いだろう。 逆に廃止ではなく、C99やC11で導入された機能というのも多々ある。例えば、C99ではint16_tのようなビット数が明示された整数型が、C11ではメモリアライメント関連の機能が追加されている。 運良く現在のコンパイラが利用したい機能をサポートしており、将来的にもサポートが失われない見通しがあるのならば、後から導入された機能を使うというのは、とても理にかなっている。なぜなら標準規格の機能は、コンパイラ開発者によってデバッグ済みであり、マニュアルがあり、そして、怪しげなトリックとも縁が無いからである。

C90に制約される事情がある場合でも、C99やC11を意識した工夫は可能である。例えば、C90より後に導入された機能でも、型や関数などC90でも等価なものを実現可能な機能がある。C90の環境でこういった機能が必要になったときに、C99やC11と足並みを揃えた追加コードの形に実装をまとめておけば、全ての規格で大部分は同じソースコードを使い、C90では追加コードを、新しい規格では追加コードなしでコンパイラが提供する規格準拠のものを使うということが実現できる。このような工夫は、複数のターゲット向けに複数のコンパイラを利用している場合はもちろん有用であるし、将来コンパイラを移行する可能性がゼロでないことを考えれば、新規格を考慮に入れておくことは損にはならないだろう。また、ある機能実現のための仕様やマニュアル、例えば関数のそれらが、自作なしに新規格からの流用で済んだとしたら、どれだけの開発時間削減になるかということについては、現場で実際に開発している方なら言わずもがなかと思う。

規格書の入手方法と閲覧方法

英語の規格書はISOもしくはANSIのような各国の規格機関から購入できる。日本語の規格書についてはJISから購入が可能だ。 C99に対応するJIS規格書「JIS X 3010:2003」については、日本工業標準調査会JIS検索ページにてJIS規格番号として「X3010」を検索することで閲覧が可能である。

まとめ

現在C言語には複数の規格がある。よって、文献を読むときやバグを解析するときには、規格を意識する必要がある。また、規格は同じでも、処理系による違いもあるので、開発者はターゲットとなる処理系での挙動を把握しておく必要がある。そして処理系による違いが発生しうる事項は、規格書に列挙されている。運の良いことに、日本ではC99規格書をインターネット経由で閲覧できるので、少なくともC99に対してはこれらの情報をすぐに確認できる。また、CPUやコンパイラもいつかは変わると考えれば、将来のためにC言語規格の変化を知っておくことは無駄ではないだろう。

戻る


  1. 最初のC言語規格はANSIで1989年に策定されたため、これがC89と呼ばれることもあるが、C89はC90と同等と考えて良い。また、1995年にはAmendment 1が発行されているが、これはC90に対する追加であるため、C90のグループに属していると考えて良い。