C ユーザーズガイド

古い形式の関数と新しい形式の関数の併用

ANSI C での最大の変更点は、C++ 言語の機能である関数プロトタイプを使用できることです。各関数にパラメータの数と型を指定することにより、すべての通常のコンパイルにおいて、関数呼び出しごとに (lint のように) 引数とパラメータが検査されるだけではなく、引数が (代入だけで) 自動的に関数が期待する型に変換されます。プロトタイプを使用するように変更できる (また、変更すべき) 既存の C コードが非常に多く存在するため、ANSI C には、古い形式と新しい形式の関数宣言を併用する規則が含まれています。

新しいコードを書く

まったく新しいプログラムを書くとき、ヘッダーでは、新しい形式の関数宣言 (関数プロトタイプ) を使用し、それ以外の C ソースファイルでは、新しい形式の関数宣言と関数定義を使用します。しかし、ANSI C 以前のコンパイラを持つマシンにコードを移植する可能性がある場合は、ヘッダーとソースファイルの両方で、マクロ __STDC__ (ANSI C コンパイルシステム専用に定義されている) を使用することをお勧めします。例については、「併用に関する考慮点」を参照してください。

同じオブジェクトまたは関数に対して 2 つの互換性のない宣言が同じスコープの中にある場合、ANSI C 準拠のコンパイラは診断メッセージを発行しなければなりません。すべての関数がプロトタイプで宣言および定義され、適切なヘッダーが正しいソースファイルにインクルードされている場合、すべての呼び出しは関数の定義に従うはずです。この取り決めによって、もっともありがちな C プログラミングの過りを防ぐことができます。

既存のコードを更新する

既存のアプリケーションで関数プロトタイプを利用したい場合、どれくらいのコードを変更するかによって、更新方法が異なります。

  1. 変更せずに再コンパイルする

    コードを変更しなくても、-v オプションでコンパイラを実行すると、パラメータの型と数の不一致について警告が発行されます。

  2. ヘッダーだけに関数プロトタイプを追加する

    大域的な関数へのすべての呼び出しが診断の対象になります。

  3. ヘッダーには関数プロトタイプを追加し、各ソースファイルの先頭には局所 (静的な) 関数に対する関数プロトタイプを追加する

    関数へのすべての呼び出しが診断の対象になります。ただしこの方法では、ソースファイル内で局所関数ごとに 2 回インタフェースを入力する必要があります。

  4. すべての関数宣言と関数定義を、関数プロトタイプを使用するように変更する

結果として受ける恩恵とそのための負荷を考えると、ほとんどの場合、上記の 2 か 3 が適切な選択だと言えるでしょう。ただしこれらを選択する場合、古い形式と新しい形式を併用するための規則を詳細に知っておく必要があります。

併用に関する考慮点

関数プロトタイプ宣言と古い形式の関数定義がともに機能するためには、両方が機能的に同じインタフェースを指定しなければなりません。つまり、ANSI C の用語を使用する「互換形式」を持っていなければなりません。

可変引数を持つ関数の場合は、ANSI C の省略記号と古い形式の varargs() 関数定義を併用することはできません。固定数のパラメータを持つ関数の場合、以前の実装で渡したとおりのパラメータの型を指定するだけです。

K&R C では、各引数は、呼び出された関数に渡される直前に、デフォルトの引数拡張に従って変換されました。このような拡張では、int より狭いすべての整数型を int サイズに拡張し、また、任意の float 引数を double に拡張するように指定されていたため、コンパイラとライブラリの両方が単純化されていました。関数プロトタイプを使用すると、よりわかりやすく表現できます。つまり、指定したパラメータの型が、そのまま、関数に渡されるパラメータの型となります。

したがって、既存の (古い形式の) 関数定義用に関数プロトタイプを書く場合、関数プロトタイプに次の型のパラメータは使用できません。

表 E-1 関数プロトタイプに使用できないパラメータ
 char signed char unsigned char float
 short signed short unsigned short  

プロトタイプを書く際には、さらに 2 つの問題があります。typedef 名と、狭い unsigned 型の拡張規則です。

古い形式の関数内のパラメータが typedef 名で宣言されている場合 (off_tino_t など)、typedef 名がデフォルトの引数拡張によって影響を受ける型を指しているかどうかを確認することが重要です。上記 2 つの typedef 名 を例にすると、off_tlong です。したがって、関数プロトタイプで使用することは適切な使用方法です。しかし、ino_tunsigned short であったため、プロトタイプで使用すると、古い形式の定義とプロトタイプが異なる互換性のないインタフェースを指定するため、診断メッセージが発行されます。

最後の問題は、unsigned short の代わりに何を使用するかです。K&R C と ANSI C コンパイラ間の最大の非互換性の 1 つは、unsigned charunsigned shortint 値に広げるための拡張規則です (「拡張: 符号なし保存と値の保持」を参照)。このような古い形式のパラメータにあたる型は、コンパイル時に使用するコンパイルモードによって異なります。

最良の方法は、int または unsigned int のどちらかを指定するように古い形式の定義を変更して、一致する型を関数プロトタイプで使用することです。必要であれば、関数を入力した後でも、より狭い型の値を局所変数に代入できます。

前処理によって影響を受ける可能性のあるプロトタイプでは、ID の使用に気をつけてください。次の例を考えてください。


#define status 23
    void my_exit(int status);   /* 通常、スコープはプロトタイプで始まり、*/
                                /* プロトタイプで終わる */

関数プロトタイプは、狭い型を持つ古い形式の関数定義と併用できません。


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 *);
    /* .  .  .  */
    #endif
    static void
    del(p)
    MyType *p;
    {
    /* .  .  .  */
    }
    /* .  .  .  */