web-dev-qa-db-ja.com

va_endは正確には何のためのものですか?常にそれを呼び出す必要がありますか?

_va_end_-_arg_ptr_をリセットするマクロ。

可変引数リストにアクセスした後、_arg_ptr_ポインターは通常va_end()でリセットされます。リストを繰り返したい場合は必須だと思いますが、そうしない場合は本当に必要ですか? 「常にswitchに_default:_を含める」というルールのように、それはちょうど良い習慣ですか?

55
Yarik

_va_end_はクリーンアップを行うために使用されます。スタックを壊したくないですよね?

_man va_start_から:

va_end()

Va_start()の各呼び出しは、同じ関数内の対応するva_end()の呼び出しと一致する必要があります。 va_end(ap)の呼び出し後、変数apは未定義です。それぞれがva_start()とva_end()で囲まれたリストの複数のトラバーサルが可能です。 va_end()は、マクロまたは関数の場合があります。

単語の存在に注意してください必須

va_start()が何をしているのかわからないため、スタックが破損する可能性があります。 _va_*_マクロは、ブラックボックスとして扱われることを目的としています。すべてのプラットフォーム上のすべてのコンパイラは、そこでやりたいことを何でも実行できます。何もしない場合もあれば、多くの場合もあります。

一部のABIは、レジスターの最初のいくつかの引数を渡し、残りはスタックに渡します。 va_arg()はもっと複雑かもしれません。特定の実装が可変引数をどのように実行するかを調べることができます。これは興味深いかもしれませんが、移植可能なコードを作成する際には、それらを不透明な操作として扱う必要があります。

47
greyfade

Linux x86-64では、va_list変数に対して実行できるトラバーサルは1つだけです。さらにトラバーサルを行うには、最初にva_copyを使用してコピーする必要があります。 man va_copyは詳細を説明しています:

va_copy()

明らかな実装では、va_listが可変個引数関数のスタックフレームへのポインタになります。そのような設定(はるかに一般的)では、割り当てに反対するものは何もないようです

   va_list aq = ap;

残念ながら、それを(長さ1の)ポインターの配列にするシステムもあり、必要なものが1つあります。

   va_list aq;
   *aq = *ap;

最後に、引数がレジスタで渡されるシステムでは、va_start()がメモリを割り当て、そこに引数を格納し、va_arg()がリストをステップスルーできるように、次の引数を示す必要がある場合があります。これで、va_end()は割り当てられたメモリを再び解放できます。この状況に対応するために、C99はマクロva_copy()を追加して、上記の割り当てを次のように置き換えることができるようにします。

   va_list aq;
   va_copy(aq, ap);
   ...
   va_end(aq);

Va_copy()の各呼び出しは、同じ関数内の対応するva_end()の呼び出しと一致する必要があります。 va_copy()を提供しない一部のシステムには、ドラフト提案で使用されている名前であるため、代わりに__va_copyがあります。

13

一般的な「スタックに渡されるパラメーター」の実装では、va_end()は通常nothing/empty/nullであると思います。ただし、従来のスキームが少ないプラットフォームでは、それが必要になります。プラットフォームに中立を保つためにそれを含めることは「グッドプラクティス」です。

12
James Curran