printfのような可変個引数関数は、取得した引数の数をどのように見つけることができますか?
引数の量は明らかに(非表示の)パラメーターとして渡されません(ここのasmの例でprintfの呼び出しを参照してください)。
トリックは何ですか?
printfのような可変個引数関数は、取得した引数の数をどのように見つけることができますか?
引数の量は明らかに(非表示の)パラメーターとして渡されません(ここのasmの例でprintfの呼び出しを参照してください)。
トリックは何ですか?
秘訣は、なんとかして彼らに話すことです。printf
型情報も含むフォーマット文字列を指定する必要があります(ただし、正しくない可能性があります)。この情報を提供する方法は、主にユーザー契約であり、多くの場合エラーが発生しやすくなります。
呼び出し規約について:通常、引数は左から右にスタックにプッシュされ、最後にバックジャンプアドレスにプッシュされます。呼び出し元のルーチンはスタックをクリアします。したがって、呼び出されたルーチンがパラメータの数を知る必要はありません。
編集:C ++ 0xには、可変個引数関数を呼び出すための安全な方法(typesafeでも!)があります!
暗黙的に、フォーマット文字列から。stdarg.hには、渡された引数の「可変」数の合計を取得するためのマクロが含まれていないことに注意してください。これは、コードサイズが大きくなっても、C呼び出し規約で呼び出し元がスタックをクリーンアップする必要がある理由の1つでもあります。
これが、C呼び出し規約で引数が逆の順序でプッシュされる理由です。例:
電話をかける場合:
printf("%s %s", foo, bar);
スタックは次のようになります。
...
+-------------------+
| bar |
+-------------------+
| foo |
+-------------------+
| "%s %s" |
+-------------------+
| return address |
+-------------------+
| old frame pointer | <- frame pointer
+-------------------+
...
引数は、フレームポインターからのオフセットを使用して間接的にアクセスされます(フレームポインターは、スタックポインターから計算する方法を知っているスマートコンパイラーによって省略できます)。このスキームでは、最初の引数は常に既知のアドレスにあり、関数は最初の引数が指示する数の引数にアクセスします。
次のことを試してください。
printf("%x %x %x %x %x %x\n");
これにより、スタックの一部がダンプされます。
AMD64 System V ABI( Linux、Mac OS X)はal
、標準のIA-32呼び出し規約とは異なり、(RAXの下位バイト)で数値ベクトル(SSE / AVX)変数を渡します。参照:printfを呼び出す前に%eaxがゼロになるのはなぜですか?
ただし、最大8個(使用するレジスタの最大数)のみです。また、IIRC、ABIでは、XMM / YMM / ZMM引数の実際の数より大きくal
することができますが、小さくすることはできません。したがって、一般的に、FP引数の数を常に示しているわけではありません。8を超える数はわかりませんが、過大評価することは許可されています。al
「3.5.7変数引数リスト」に記載されている「レジスタ保存領域」への不要なベクトルレジスタの保存をスキップすることは、パフォーマンス上の理由でのみ使用できます。たとえば、GCCはal!=0
、XMM0..7をテストしてから、スタックにダンプするか、何もダンプしないコードを作成します。(または、関数がどこでも使用VA_ARG
する場合は__m256
、YMM0..7。)
経営幹部レベルでは、他の人が述べているようにフォーマット文字列を解析する以外にも他の手法があります。また、次のこともできます。
execl(void *)0
のように、最後の引数を示すために番兵を渡します。
関数属性を使用して、sentinel
GCCがコンパイル時にそれを強制できるようにする必要があります。C警告関数呼び出しにセンチネルがありません
varargsの数を含む追加の整数引数として渡します
function属性を使用して、format
GCCがprintf
またはなどの既知のタイプのフォーマット文字列を適用できるようにします。strftime