ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: C ユーザーガイド Oracle Solaris Studio 12.3 Information Library (日本語) |
1990 ISO C 規格での最大の変更点は、C++ 言語の機能である関数プロトタイプを使用できることです。各関数のパラメータの数と型を指定することにより、すべての通常のコンパイルは、関数呼び出しごとに (lint のように) 引数とパラメータの検査の利点を得る一方、引数が (代入だけで) 自動的に関数が期待する型に変換されます。プロトタイプを使用するように変更できる (また、変更すべき) 既存の C コードが非常に多く存在するため、1990 ISO C 規格には、古い形式と新しい形式の関数宣言を併用する規則が含まれています。
1999 ISO C 規格によって、古い形式の関数宣言は廃止されました。
まったく新しいプログラムを書くとき、ヘッダーでは、新しい形式の関数宣言 (関数プロトタイプ) を使用し、それ以外の C ソースファイルでは、新しい形式の関数宣言と関数定義を使用します。しかし、ISO C 以前のコンパイラを持つシステムにコードを移植する可能性がある場合は、ヘッダーとソースファイルの両方で、マクロ __STDC__ (ISO C コンパイルシステム専用に定義されている) を使用してください。例については、「6.2.3 併用に関する考慮点」を参照してください。
同じオブジェクトまたは関数に対して 2 つの互換性のない宣言が同じスコープの中にある場合、ISO C 準拠のコンパイラは診断メッセージを発行しなければいけません。すべての関数がプロトタイプで宣言および定義され、適切なヘッダーが正しいソースファイルによってインクルードされている場合、すべての呼び出しは関数の定義に従うはずです。この取り決めによって、起こりがちな C プログラミングの誤りを防ぐことができます。
既存のアプリケーションがあり、関数プロトタイプの利点が必要な場合、どれくらいのコードを変更するかによって、更新にいくつかの可能性が存在します。
変更せずに再コンパイルする
コードを変更しなくても、-v オプションでコンパイラを実行すると、パラメータの型と数の不一致について警告が発行されます。
ヘッダーだけに関数プロトタイプを追加する
大域的な関数へのすべての呼び出しが診断の対象になります。
ヘッダーには関数プロトタイプを追加し、各ソースファイルの先頭には局所 (静的な) 関数に対する関数プロトタイプを追加する
関数へのすべての呼び出しが対象になります。ただしこの方法では、ソースファイル内で局所関数ごとに 2 回インタフェースを入力する必要があります。
すべての関数宣言と関数定義を、関数プロトタイプを使用するように変更する
結果として受ける恩恵とそのための負荷を考えると、ほとんどの場合、前述の 2 か 3 が適切な選択だと言えるでしょう。ただしこれらを選択する場合、古い形式と新しい形式を併用するための規則を詳細に知っておく必要があります。
関数プロトタイプ宣言と古い形式の関数定義がともに機能するためには、両方が機能的に同じインタフェースを指定しなければいけません。つまり、ANSI/ISO C の用語を使用する「互換形式」を持っていなければいけません。
可変引数を持つ関数の場合は、ISO C の省略記号と古い形式の varargs() 関数定義は併用できません。固定数のパラメータを持つ関数の場合、以前の実装で渡したとおりのパラメータの型を指定できます。
K&R C では、各引数は、呼び出された関数に渡される直前に、デフォルトの引数拡張に従って変換されました。 このような拡張は、int より狭いすべての整数型が int サイズに拡張され、また、任意の float 引数が double に拡張されるように指定していたため、コンパイラとライブラリの両方を単純化していました。関数プロトタイプはより表現力が高いです。指定したパラメータの型が関数に渡されるものになるためです。
したがって、関数プロトタイプが既存の (古い形式の) 関数定義のために記述されている場合、その関数プロトタイプには、次のいずれかの型のパラメータを一切含めないようにしてください: char、signed char、unsigned char、float、short、signed short、unsigned short。
プロトタイプを書く際には、依然として 2 つの問題があります。typedef 名と、狭い unsigned 型の拡張規則です。
古い形式の関数内のパラメータが typedef 名を使って宣言されている場合 (off_t や ino_t など)、typedef 名がデフォルトの引数拡張によって影響を受ける型を指しているかどうかを確認する必要があります。これら 2 つの場合、off_t は long で、関数プロトタイプで使用できます。ino_t は unsigned short であったため、プロトタイプで使用すると、古い形式の定義とプロトタイプが異なる互換性のないインタフェースを指定するため、コンパイラは診断メッセージを発行します。
unsigned short の代わりに何を使用すべきかを決定するのは、複雑です。K&R C と 1990 ANSI/ISO C コンパイラ間の最大の非互換性は、unsigned char と unsigned short を int 値に広げるための拡張規則です (「6.4 拡張: 符号なし保存と値の保持」を参照)。この古い形式のパラメータに対応するパラメータ型は、コンパイル時に使用するコンパイルモードによって異なります。
-Xs と -Xt では unsigned int を使用する
-Xa と -Xc では int を使用する
最良の方法は、int または unsigned int のどちらかを指定するように古い形式の定義を変更して、一致する型を関数プロトタイプで使用することです。必要であれば、関数を入力したあとでも、より狭い型の値を局所変数に代入できます。
前処理によって影響を受ける可能性のあるプロトタイプでは、ID の使用に注意を払ってください。次の例を考えてみましょう。
#define status 23 void my_exit(int status); /* Normally, scope begins */ /* and ends with prototype */
関数プロトタイプは、狭い型を持つ古い形式の関数定義と併用できません。
void foo(unsigned char, unsigned short); void foo(i, j) unsigned char i; unsigned short j; {...}
__STDC__ を適切に使用すれば、古いコンパイラと新しいコンパイラの両方で使用できるヘッダーファイルを作成できます。
header.h: struct s { /* . . . */ }; #ifdef __STDC__ void errmsg(int, ...); struct s *f(const char *); int g(void); #else void errmsg(); struct s *f(); int g(); #endif
次の関数はプロトタイプを使用していますが、古いシステムでもコンパイルできます。
struct s * #ifdef __STDC__ f(const char *p) #else f(p) char *p; #endif { /* . . . */ }
次の例は、更新されたソースファイルを示しています (前述の選択肢 3 と同様)。局所関数は古い形式の定義を使用していますが、新しいコンパイラ用にプロトタイプも含まれています。
source.c: #include “header.h” typedef /* . . . */ MyType; #ifdef __STDC__ static void del(MyType *); /* . . . */ static void del(p) MyType *p; { /* . . . */ } /* . . . */