web-dev-qa-db-ja.com

Cでの可変引数の使用例

ここ 可変引数をCで使用する方法の例を見つけました。

_#include <stdarg.h>

double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}
_

この例はある程度しか理解できません。

  1. va_start(ap, count);を使用する理由は明確ではありません。私の知る限り、この方法でイテレータを最初の要素に設定します。しかし、なぜデフォルトで先頭に設定されていないのですか?

  2. countを引数として指定する必要がある理由は明確ではありません。 Cは引数の数を自動的に決定できませんか?

  3. va_end(ap)を使用する理由は明確ではありません。何が変わりますか?反復子をリストの最後に設定しますか?しかし、ループによってリストの最後に設定されていませんか?さらに、なぜそれが必要なのでしょうか? apはもう使用しません。なぜ変更したいのですか?

34
Roman

引数はスタックで渡されることに注意してください。 va_start関数には、正しいスタックポインターでva_listを初期化する「マジック」コードが含まれています。 必須関数宣言で最後に指定された引数を渡さないと機能しません。

va_argが行うことは、この保存されたスタックポインターを使用し、提供された型の正しいバイト量を抽出し、apを変更してスタック上の次の引数を指すようにします。


実際には、これらの関数(va_startva_argおよびva_end)は実際には関数ではなく、プリプロセッサマクロとして実装されています。実際の実装もコンパイラに依存します。異なるコンパイラはスタックのレイアウトとスタック上の引数のプッシュ方法が異なるためです。

34

しかし、なぜデフォルトで先頭に設定されていないのですか?

たぶん、コンパイラが十分に賢くなかったときからの歴史的な理由のためです。たぶん、あなたは実際に可変引数を気にしない可変引数関数プロトタイプを持っているかもしれないし、可変引数の設定はその特定のシステムでたまたま費用がかかるからだ。 va_copyを実行するより複雑な操作のためか、引数の操作を複数回再開し、va_startを複数回呼び出したい場合があります。

短いバージョンは次のとおりです。言語標準がそう言っているからです。

第二に、なぜ私たちが議論として数える必要があるのか​​、私には明らかではありません。 C++は引数の数を自動的に決定できませんか?

countだけではありません。これは、関数の最後の名前付き引数です。 va_startは、可変引数の場所を特定するために必要です。ほとんどの場合、これは古いコンパイラの歴史的な理由によるものです。今日、どうしてこれを別の方法で実装できなかったのかわかりません。

質問の2番目の部分として:いいえ、コンパイラーは関数に送信された引数の数を知りません。同じコンパイルユニットまたは同じプログラムにない場合もあり、コンパイラは関数がどのように呼び出されるかを知りません。 printfのような可変引数関数を持つライブラリを想像してください。 libcをコンパイルするとき、コンパイラはprintfをいつどのように呼び出すかを知りません。ほとんどのABI(ABIは関数の呼び出し方法、引数の受け渡し方法などの規則)では、関数呼び出しが取得した引数の数を調べる方法はありません。関数呼び出しにその情報を含めるのは無駄であり、ほとんど必要ありません。そのため、varargs関数に引数の数を伝える方法が必要です。実際に渡された引数の数を超えてva_argにアクセスすることは、未定義の動作です。

それから、なぜva_end(ap)を使用するのか明確ではありません。何が変わりますか?

ほとんどのアーキテクチャでは、va_endは何も関係ありません。しかし、セマンティクスを渡す複雑な引数を持ついくつかのアーキテクチャがあり、va_startは潜在的にメモリをmallocする可能性があり、そのメモリを解放するにはva_endが必要になります。

ここの短いバージョンもあります:言語標準がそう言うので。

6
Art

va_startは、可変引数のリストを初期化します。常に最後に名前を付けた関数の引数を2番目のパラメーターとして渡します。なぜなら、引数はスタックにプッシュされ、コンパイラーは変数引数リストの始まりの位置を知ることができないからです(区別はありません)。

Va_endに関しては、va_start呼び出し中に変数引数リストに割り当てられたリソースを解放するために使用されます。

6
W.B.

Cマクロです。 va_startは、最初の要素のアドレスに内部ポインターを設定します。 va_endクリーンアップva_list。コードにva_startがあり、va_endがない場合-UBです。

ISO Cがヘッダーのva_start()マクロへの2番目のパラメーターに課す制限は、この国際標準では異なります。パラメーターparmNは、関数定義の変数パラメーターリスト(...の直前のもの)の右端のパラメーターの識別子です。パラメーターparmNが、関数、配列、または参照型、またはパラメーターのない引数を渡すときに生じる型と互換性のない型で宣言されている場合、動作は未定義です。

3
ForEveR