C ユーザーズガイド

可変引数を持つ関数

以前の実装では、関数が期待するパラメータの型を指定できませんでした。しかし、ANSI C でプロトタイプを使用すれば、これを指定できます。printf() などの関数をサポートするために、プロトタイプの構文では特別な省略記号 (...) が終了記号として使用されます。実装によっては可変引数を処理するために特別なことを行う必要があるため、ANSI C では、すべての宣言とこのような関数などの定義が末尾に省略記号を含むべきであると規定しています。

パラメータの「...」部分には名前がないため、stdarg.h に含まれている特別なマクロにはこれらの引数へアクセスするための関数が含まれています。初期のバージョンではこのような関数は varargs.h に含まれている同様なマクロを使用しなければなりませんでした。

次に、これから書こうとする関数が errmsg() というエラーハンドラであると仮定します。この関数は void を返し、固定パラメータとして、エラーメッセージの詳細を指定する int だけを持つと仮定します。このパラメータの後には、ファイル名または行番号 (あるいは、その両方) を指定できます。そして、ファイル名または行番号の後には、(printf() のような) エラーメッセージのテキストを指定する書式と引数を指定できます。

初期のコンパイラで上記例をコンパイルするには、ANSI C コンパイルシステム専用に定義されたマクロ __STDC__ を多く使用する必要があります。したがって、適切なヘッダーファイルにおける関数の宣言は次のようになります。


#ifdef __STDC__
    void errmsg(int code, ...);
#else
    void errmsg(); #endif

errmsg() の定義を持つファイルは、古い形式と新しい形式を併用できます。まず、インクルードするヘッダーはコンパイルシステムによって異なります。


#ifdef __STDC__
#include <stdarg.h> #else #include <varargs.h>
#endif
#include <stdio.h>

その後で fprintf()vfprintf() を呼び出すため、stdio.h をインクルードしています。

次は関数の定義です。識別子 va_alistva_dcl は古い形式の varargs.h インタフェースの一部です。


void
#ifdef __STDC__
errmsg(int code, ...)
#else
errmsg(va_alist) va_dcl   /* 注: セミコロンなし */
#endif
{
    /* more detail below */
}

古い形式の変数引数メカニズムでは固定パラメータを指定できないため、固定パラメータは、可変部分の前でアクセスされるように配置しなければなりません。また、パラメータの「...」部分に名前がないため、新しい va_start() マクロは 2 番目の引数 (「...」の直前にあるパラメータの名前) を持ちます。

拡張として、Sun ANSI C では、固定パラメータなしで関数を宣言および定義できます。

int f(...);

このような関数の場合、va_start() は 2 番目の引数を空にして呼び出す必要があります。

va_start(ap,)

次は関数の本体です。


{
    va_list ap;
    char *fmt;
#ifdef __STDC__
    va_start(ap, code);
#else
    int code;

    va_start(ap);
    /* 固定引数を抽出する */
    code = va_arg(ap, int);
#endif
    if (code & FILENAME)
        (void)fprintf(stderr, "¥"%s¥": ", va_arg(ap, char *));
    if (code & LINENUMBER)
        (void)fprintf(stderr, "%d: ", va_arg(ap, int));
    if (code & WARNING)
        (void)fputs("warning: ", stderr);
    fmt = va_arg(ap, char *);
    (void)vfprintf(stderr, fmt, ap);
    va_end(ap); }

va_arg()va_end() マクロは両方とも古い形式と ANSI C バージョンで同様に動作します。va_arg()ap の値を変更するため、vfprintf() への呼び出しを次のようにすることはできません。


(void)vfprintf(stderr, va_arg(ap, char *), ap);

マクロ FILENAMELINENUMBER、および WARNING の定義は、おそらく、errmsg() の宣言と同じヘッダーに含まれています。

errmsg() への呼び出しの例は次のようになります。


errmsg(FILENAME, "<command line>", "cannot open: %s¥n", argv[optind]);