便乗質問になってしまうのですが、話が変わるので別スレッドを起こします。
可変長引数をとる関数内から、可変長引数を取る関数を直接呼ぶ方法は存在する
のでしょうか?
前々からの素朴な疑問ですので、ご存じの方がいらしたら教えていただけると幸
いです。
以下のようなものです。
void TEST1(LPCTSTR Fmt, ...)
{
TEST2(Fmt, ...);
}
void TEST2(LPCTSTR Fmt, ...)
{
}
このままでは、もちろん、
error C2059: 構文エラー : '...'
となります。
よろしくお願いいたします。
C/C++ ソースレベルでの移植性を保ったまま、では存在しない。
自分でアセンブラ手書きするとか、そういうことをすれば可能だろうけど。
というわけで、可変個数引数関数を作る場合は FormatV なり vprintf なりの
va_list を受け取る関数を同時に用意するのが常套手段なわけだ。
っていうか例えば CString のソースを見るとわかるけど
void CString::Format(LPTCSTR format, ...) {
va_list arglist;
va_start(arglist, format);
FormatV(format, arglist);
va_end(arglist);
}
のように実装するのが再利用性を増すためにとられる手段なわけだ。
FormatV なり vprintf なりのほうが本体処理を担当。
Format や printf など ... を直接受け取る関数はただの Wrapper にする、
ってことだな。発想を逆にしておくといいよ。
自分で可変長引数を扱おうと思ったら
tetrapodさんが書かれているva_listとva_start、va_endを使う事になります。
これについては上記のキーワードで調べて見てください。
使用例が書いてあるホームページもあると思いますから。
可変長引数の場合、可変長部分はデータ型を特定できないので
printf等のようにフォーマット文字列を使ってどういうデータ型のデータが
どういう順番でわたってくるのかと言うのを示してあげないと取り出せないです。
で、この部分を自分で実装するとなるとものすごくめんどくさい事になるので
通常は自分で組んだりはしないです。
tetrapodさんが書かれているようにvprintf等の関数を使って
実績のある実装を使う方法が一般的だと思います。
あえて、自分で実装してみようというのであれば、止めませんが、
かなりめんどくさいことは確かです。
一回自分でやってみるとなぜ、フォーマット文字列を間違えると
プログラムが落ちてしまうのか実感できるかも。
やはり、簡単な方法は無いのですね。
可変長引数の内部処理は、以前に少し追ったことがあります。
単純に複数の引数がメモリ上の連続領域として取られて(引数スタック?)、それを順に
解析していたような記憶があります。
だから、連続領域の先頭アドレスを扱うと何とかなるのかな?
と思ったのですが、甘かったようです。
>だから、連続領域の先頭アドレスを扱うと何とかなるのかな?
>と思ったのですが、甘かったようです。
この気持ちは分かります。
でも処理系によって引数スタックの積み方が違うじゃんね。
左順からとか、右順から評価されたりするから。
C/C++の規格では既定されていないので
コンパイラを特定してインライン・アセンブラを使えば
出来そうですが可読性がない、保持性もない、移植性はまったくなくなるね。
僕も興味深くこの質問を見ていましたが
やっぱりスマートな方法はなさそうですね。
僕も納得。
(若い頃、vsprintf のミニ版を作ったが面倒でしたよ)
可変長引数の所って通常の関数で言う参照渡しではないから
だと思います。ここの説明はかなり面倒です。
可変長引数の所に関しては、引数として引き渡された値が
単純にスタック上に連続で詰まれますよね。
引き渡した値が何かのポインタだったとしてもそれに関しては
お構い無しです。要はアドレスを示す数値が値渡しで積まれます。
つまり、可変長引数の場合は引数で渡された内容が値渡しで
引き渡されるわけです。なので、可変長引数の内容が格納されている
変数のアドレスを引き渡されたとしても可変長引数が求める引き渡し方に
なっていない事になります。
可変長引数で渡された内容をそのまま呼び出される側に引き渡す必要が
ある為、上記のような特殊な渡し方になるわけです。
何が言いたいのかさっぱりわからん・・・
変数のアドレスを引き渡す可変個数引数関数などごく普通にある scanf とか
俺が言いたいのは
Format と FormatV のペア、printf と vprintf のペアのように
可変個数引数 ... を直接受け取る関数と、va_list を受け取る関数とを
ペアで作っておくべし(自分で作るなら)、ということだけ。
逆に、ペアになっていないようなライブラリがあったら俺はその作者の技量を疑う。
まともなライブラリなら ... と va_list の両者の関数が必ずペアで提供されている
はずなので、そもそも可変個数引数関数から可変個数引数を直接呼ぶ必然がない。
... な関数からは va_list な引数を呼べば事足りる、っつこと。
>だから、連続領域の先頭アドレスを扱うと何とかなるのかな?
>と思ったのですが、甘かったようです。
に対する書込みです。
要は引き渡し方が特殊だからそういう引き渡し方では無理ですねと言う話。
tetrapodさんの書き込みは私も賛成でそういう風に作っておけば、
使い回しも聞くでしょと言う話ですよね。
あんまり自分でゴリゴリ作るところではない気はしますね。
可変長引数だと型チェックも出来ないし。
この手のが必要なのってデバッグ時のログ取り用の関数くらいしか
思いつかないしなぁ。
scanfの場合は引き渡された値をフォーマット文字列の内容に合わせて
どういう変数のポインタか判断して処理しているのではないかなぁ。
受け取る側がva_list内の値の解釈をして判断していると言う意味では
同じだと思うんですが。
実際にはあんまり重要ではない話かもしれないですねぇ。
tetrapodさんの言うように
「Format と FormatV のペア、printf と vprintf のペアのように
可変個数引数 ... を直接受け取る関数と、va_list を受け取る関数とを
ペアで作っておくべし(自分で作るなら)、ということだけ。
逆に、ペアになっていないようなライブラリがあったら俺はその作者の技量を疑う。
まともなライブラリなら ... と va_list の両者の関数が必ずペアで提供されている
はずなので、そもそも可変個数引数関数から可変個数引数を直接呼ぶ必然がない。
... な関数からは va_list な引数を呼べば事足りる、っつこと。」
で十分かも。