参照パラメーター付きの可変引数を使用する必要がありますか
-
03-07-2019 - |
質問
このコード(要約)があります...
AnsiString working(AnsiString format,...)
{
va_list argptr;
AnsiString buff;
va_start(argptr, format);
buff.vprintf(format.c_str(), argptr);
va_end(argptr);
return buff;
}
そして、可能な場合は参照渡しが好ましいとの理由で、私はそれをこのように変更しました。
AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}
私の呼び出しコードは次のようなものです:-
AnsiString s1, s2;
s1 = working("Hello %s", "World");
s2 = broken("Hello %s", "World");
しかし、s1には<!> quot; Hello World <!> quot;が含まれ、s2には<!> quot; Hello(null)<!> quot;が含まれます。これはva_startの動作方法によるものだと思いますが、何が起こっているのか正確にはわかりません。
解決
va_startの展開先を見ると、何が起こっているのかがわかります:
va_start(argptr, format);
なる(おおよそ)
argptr = (va_list) (&format+1);
formatが値型の場合、すべての可変引数の直前にスタックに配置されます。形式が参照タイプの場合、アドレスのみがスタックに配置されます。参照変数のアドレスを取得すると、引数のアドレスではなく、アドレスまたは元の変数(この場合、Brokenを呼び出す前に作成された一時的なAnsiStringの)を取得します。
完全なクラスを渡したくない場合、オプションはポインターで渡すか、仮引数を入れるかです:
AnsiString working_ptr(const AnsiString *format,...)
{
ASSERT(format != NULL);
va_list argptr;
AnsiString buff;
va_start(argptr, format);
buff.vprintf(format->c_str(), argptr);
va_end(argptr);
return buff;
}
...
AnsiString format = "Hello %s";
s1 = working_ptr(&format, "World");
または
AnsiString working_dummy(const AnsiString &format, int dummy, ...)
{
va_list argptr;
AnsiString buff;
va_start(argptr, dummy);
buff.vprintf(format.c_str(), argptr);
va_end(argptr);
return buff;
}
...
s1 = working_dummy("Hello %s", 0, "World");
他のヒント
C ++標準(18.7-その他のランタイムサポート)がva_start()
(強調鉱山)について述べていること:
ISO Cが課す制限 の2番目のパラメーター
<stdarg.h>
ヘッダー内のマクロparmN
はこれで異なります 国際標準。パラメーター...
は、 変数の右端のパラメーター 関数のパラメーターリスト 定義(直前の <=>)。 パラメータ<=>が関数、配列、または参照で宣言されている場合 タイプまたはそうでないタイプ 結果の型と互換性があります 引数を渡すとき パラメーターはありません。動作 未定義。
他の人が述べたように、C ++で可変引数を使用することは、非直線Cアイテムで使用する場合(そして場合によっては他の方法でも)使用するのは危険です。
それは言った-私はまだprintf()を常に使用しています...
これが望ましくない理由の良い分析は、 N0695
C ++コーディング標準(Sutter、Alexandrescu)によると:
varargsはC ++では使用しないでください:
これらはタイプセーフではなく、クラスタイプのオブジェクトに対して未定義の動作をするため、問題が発生する可能性があります。
これは私の簡単な回避策です(Visual C ++ 2010でコンパイル):
void not_broken(const string& format,...)
{
va_list argptr;
_asm {
lea eax, [format];
add eax, 4;
mov [argptr], eax;
}
vprintf(format.c_str(), argptr);
}
サイドノート:
可変引数としてのクラス型の動作は未定義かもしれませんが、私の経験では一貫しています。コンパイラは、クラスのメモリのsizeof(class)をスタックにプッシュします。すなわち、擬似コードで:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
これが非常に創造的な方法で利用されている非常に興味深い例については、LPCTSTRの代わりにCStringインスタンスをvarargs関数に直接渡すことができることに注意してください。キャストは含まれません。読者にそれをどのように機能させたかを理解するための演習として残しておきます。