[C言語 標準ライブラリ] stdarg.hの使い方

※本ブログでは、商品・サービスのリンク先にプロモーションを含みます。

スポンサーリンク

stdarg.hには、printf 関数のような可変長引数に関する型、マクロ関数が宣言、定義されています。

スポンサーリンク

va_list

可変長引数にアクセスするための情報を格納する型です。

マクロ関数

void va_start(va_list ap, parmN)

可変長引数(実引数)の情報の取得を開始します。
実引数にアクセスする前に呼び出す必要があります。
va_start()以降に呼び出されるva_arg()、va_endの呼び出しに備えてva_list型のapを初期化します。
parmNは、仮引数の識別子(…)の直前の識別子です。
parmNがregister記憶域クラス、関数型、配列型、引数型変換を行った後の型が元の型と互換性が無い場合は、動作は未定義(環境依存)となります。

type va_arg(va_list ap, type)

可変長引数(実引数)を取得します。
2番目の引数(type)と同じ型の返り値を持つように展開されます。
va_list型のapはva_start()で初期化されたものでなければなりません。
va_arg()の実行によって、apは順番に引数を返すように変更されます。
引数の型typeは、実際の引数の型へのポインタ型であり、特定された型はtypeの前にポインタ(*)を添えることで取得できます。
va_start()実行後の最初のva_arg()の実行では、va_start()で指定したparmNの直後の引数の値を返します。
続けて実行すれば、残りの引数の値を順番に返します。

void va_end(va_list ap)

可変長引数(実引数)の取得を終了します。
va_list apを初期化したva_start()の展開によって参照された可変長引数をもつ関数からの正常な復帰を可能にします。
va_end()実行後はapが更新され使用できなくなる場合があります。(実装依存)

void va_copy(va_list dest, va_list src)

destにva_start()を適用し、それに続いてva_arg()を使った場合と同じ規則にしたがって、destをsrcのコピーとして初期化します。
destに対してva_end()が呼び出されることはありません。

va_start、va_arg、va_endの使用例

これらの関数は、可変長引数リスト(関数が任意の数の引数を取ることができる機能)を操作するために使用されます。以下に、それらの関数を使ってprintfのような関数を作成する例を示します。

#include <stdio.h>
#include <stdarg.h>

void my_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);  // 引数リストを初期化します

    while(*format != '\0'){
        if(*format == 'd'){
            int i = va_arg(args, int);  // 次の引数を取得します
            printf("%d", i);
        }else if(*format == 'c'){
            // 'char'引数が整数として昇格されることに注意
            int c = va_arg(args, int);
            printf("%c", c);
        }else if(*format == 'f'){
            double d = va_arg(args, double);
            printf("%f", d);
        }
        ++format;
    }

    va_end(args);  // 引数リストをクリーンアップします
}

int main(void)
{
    my_printf("dcf", 3, 'a', 1.23);

    return 0;
}

実行結果:

3a1.230000

va_copyの使用例

va_copyはva_listを別のva_listにコピーするための関数です。
これは元のva_listを保持したまま新しいva_listを操作したい場合などに役立ちます。
以下に具体的な使用例を示します。

#include <stdarg.h>
#include <stdio.h>

void print_two(const char *format, ...)
{
    va_list args1, args2;

    va_start(args1, format);
    va_copy(args2, args1);

    // 最初の引数リストを使用します
    vprintf(format, args1);
    va_end(args1);

    printf("\n");

    // コピーした引数リストを再度使用します
    vprintf(format, args2);
    va_end(args2);
}

int main(void)
{
    print_two("Int: %d, Double: %.2f, Char: %c", 5, 2.3, 'A');

    return 0;
}

このプログラムでは、print_two関数が引数リストを二つ作成します。
一つ目のリスト(args1)は普通に使用され、その後va_endでクリーンアップされます。二つ目のリスト(args2)はva_copyで一つ目のリストから作成され、その後同様に使用されます。
これにより、同じ引数リストを複数回使用することが可能になります。
このプログラムは同じフォーマット文字列と引数リストを用いて2回printfを実行しています。

実行結果:

Int: 5, Double: 2.30, Char: A
Int: 5, Double: 2.30, Char: A
スポンサーリンク
C言語

コメント