ANSI-Cの違い



ANSI-Cではちょっと違う

 えっと、会社から支給されたC言語の本が、ANSI-C以前のC(いわゆる K & R C というやつかな?)で書かれているようなので、そのあたりの違いを少しだけ書いておきます。重箱の隅を突っつくようで嫌らしいのですが、私の性格なんで勘弁してください(できるか!)。



関数宣言

 ANSI-Cで一番変わったのが関数周りでしょう。まずは宣言方法から見ていきます。
 ANSI-C の関数の宣言は基本的に以下の通りです。

記憶クラス 型名 関数名(引数の型と名前)
{
  宣言と文
}
 これは基本的にC++にも受け継がれており、たとえば main 関数であれば
int main(int argc, char *argv[])
{
  ....
}
 と書きます。
 一方でANSI-Cが規格化される以前のC(K & R C)の時代では以下のように書いていました。
int main(argc, argv)
int  argc;
char *argv[];
{
  ...
}
 ただしこの書き方は現在はまず使われないもので、新いコンパイラだと「宣言方法が古いものです」などという warrning を吐いてくるものさえあります。現在のCやC++では一応はどちらの方式も通りますが、ANSI-Cに準拠させて書くべきでしょう。



プロトタイプ宣言

 ANSI-Cで一番変わったのはプロトタイプ宣言の出現によるプログラムスタイルでしょう。

 プロトタイプとは関数の記憶クラス、戻り値の型、引数の型を事前に宣言するものです。Cコンパイラはソースの上部から順に処理していきますので関数呼び出しを前方参照(関数が呼び出している場所より後で宣言されている)で行うと、関数の型を呼び出し側の型と同じであるとみなして処理してしまいます。たとえば次のような例で問題が起きます。


01: #include 
02: 
03: main()
04: {
05:   printf("%lf\n", func(10));
06:   
07:   return 0;
08: }
09: 
10: double func(double a)
11: {
12:   return a * 2;
13: }

 上記の例では、func関数は引数、戻り値ともに double 型ですが、コンパイラはコンパイルする過程で上部から処理していくので 5行目の段階ではコンパイラはfunc型がどのような型か知りません。そこでコンパイラはパラメーターから、int func(int a) であるだろうと勝手に予測して処理してしまいます。そのため、コンパイル処理が10行目に達した段階で「関数の宣言型が違う!」とエラーを吐きます(余談ですがC++では宣言されていない関数を呼ぶことはできません)。
 そのため、ANSI-Cではプロトタイプ宣言を用いることで解決しています。
 またC言語ではデフォルトの型は常に int なので int の場合は宣言の省略が可能です。そのため当時のプログラムスタイルでは int の省略をよく見かけるのですが、最近の風潮から言うとこれは省略するべきではありません。コーディングの簡易化よりも以後のメンテナンス性や可読性の向上を目指したプログラムを書くべきです。
 ANSI-Cの場合の書き方で書き直したのが以下のものです。
01: #include 
02: 
03: double func(double a);  /* プロトタイプ宣言 */
04: 
05: int main()
06: {
07:   printf("%lf\n", func(10));
08:   
09:   return 0;
10: }
11: 
12: double func(double a)
13: {
14:   return a * 2;
15: }

 ここで 3行目がプロトタイプ宣言です。つまり事前に関数の型だけを宣言しておくことでコンパイラが正しく処理してくれるようになります。

 ちなみにプロトタイプ宣言が無かった時代はどうしていたかと言うと、呼び出しより先に関数を書くことで解決していました。つまり、上記の例では func 関数の下に main 関数を書いていたわけです(良く知らないけど Pascalの書き方と同じはず。一応、プロトタイプ宣言以前にも関数の使用宣言という戻り値の型だけ指定する方法がありましたが、引数の指定はできませんでしたので呼び出し側でのキャストが必要でした)。しかしこの方法ではいくつかの問題があります。
 といったところでしょうか。特にモジュール化においてはプロトタイプ宣言はモジュールの外部インターフェースですからしっかりと宣言しましょう。現在ではプロトタイプ宣言は基本ですから、前方参照が行われていなくてもとりあえず宣言するぐらいのつもりでいるべきです。



他にも違うぞ ANSI-C

 その外にも変わった部分を簡単に紹介します。


データ型の追加

 enum型と、void型が追加になりました。特にvoidは重要で、返り値の無い関数やを明示的に宣言できるようになりました。そのため最近のコンパイラでは void 宣言していない関数が返り値を返さないとウォーニングやエラーとなります。また void 型のポインタが作れるようになったことも重要でしょう。


標準ライブラリの規格化

 K & R 時代にはあいまいだった標準ライブラリやヘッダファイルが規格化されました。


プリプロセッサの機能強化

 新しいプリプロセッサ命令として#elif、defined、#erroe#pragamが追加されました。また、文字列引用の #、トークン連結の ##が利用可能となりました。


long floatの廃止、long doubleの追加

 double と同義語であった long float が廃止となり、double 以上の精度を持つ long double が追加されました。


文字列定数の結合

 隣接する文字列が結合されるようになりました。

char *buf = "Hello\nWorld\n";
と書いていたものが
char *buf = "Hello\n"
            "World\n";
とかけるようになりました。


構造体の代入と関数への受け渡し

 構造体が直接代入できるようになり、同時に関数への受け渡しも可能となりました。


三連文字の追加

 文字セットの少ない環境のために三連文字が追加されました。


 型指定子の追加

 定数の型を明確なものにするために、ULF、などの末尾子が追加になりました。


型指定子の追加

 signedconstvolatileが追加となりました。


floatの計算

 以前は floatはdoubleに変換されてから計算されていましたが、直接計算されるようになりました。そのため必要とする精度が少ないない場合は float を使った方が高速になりました。


識別子の有効長

 8文字までしか有効でなかった識別子が31文字までに拡張されました。


エスケープ文字の追加

 '\a'などのエスケープ文字が追加となりました。


共用体の初期化

 共用体の初期化

自動変数の構造体、共用体、配列の初期化

 自動変数(auto型)の構造体、共用体、配列の初期化が可能となりました。


ワイド文字とマルチバイト文字

 ワイド文字とは8bit以上の幅を持つ文字型のことで wchar_t で定義されます。またマルチバイト文字とは8bit文字とそれ以上の文字が混在しているものの事で、ANSI-Cではこれらに対する考慮が加えられました。例としてワイド文字になる漢字コードとしては UNICODEなどが有名ですね。MS漢字コード(Shif-JIS)やJISコード、EUCコード等はマルチバイトセットです。Windowsなどの各国語対応のOS上でのプログラミングでは特に気を使う必要のある部分です。



余談

 プロトタイプ宣言の話がでたのでヘッダファイルの作り方等を少々書いてみます。まずモジュール化する場合、モジュール毎にヘッダファイルを作成するのが一般的では無いでしょうか(C++ではほぼ必須ですが)。小さ目のプログラムだとヘッダ部分は一つのファイルにまとめてしまうこともありますが、大きめなものとなると分離したくなります。理由は簡単でモジュールの独立性を高めたいからです。ヘッダをまとめると言うことは他のモジュールと内容が混ざってしまうと言うことでありいい方法ではありません。独立させるということはそこだけ抜き出せるわけですから、独立にテストを行うなどもできますし、メンテナンス性の向上や再利用の可能性もでてきます。

 ではヘッダにはどのような事を書けばいいのでしょう。それはモジュールのインターフェースすべてです。「ヘッダだけを見ればそのモジュールの本体を見る必要が無い」というのが理想です。即ち外部から呼ばれるべき関数のプロトタイプ宣言と外部から参照する可能性のあるグローバル変数の extern 宣言です。サンプルとしてはC++の世界のところでスタックの例を挙げているので参照して見てください。そしてできることならヘッダにはしっかりとコメントを入れて下さい。C言語では「プロトタイプ宣言=プログラムの仕様書」的な要素がふんだんにありますからコメントをしっかりと書いておけば大変役に立ちます。