1週間以上働いているCプログラマーは、実際の引数よりも多くのフォーマット指定子を使用してprintf
を呼び出すとクラッシュが発生しました。例:
printf("Gonna %s and %s, %s!", "crash", "burn");
しかし、printfにtoo many引数を渡したときに発生する可能性のある同様の問題はありますか?
printf("Gonna %s and %s!", "crash", "burn", "dude");
X86/x64アセンブリに関する私の知識から、これは無害であると信じるようになりましたが、欠落しているEdge条件がないとは確信していません。他のアーキテクチャについては知りません。この状態は無害であることが保証されていますか、それともクラッシュを引き起こす可能性のある落とし穴もここにありますか?
あなたはおそらくこのようなものとしてprintf関数のプロトタイプを知っています
int printf(const char *format, ...);
そのより完全なバージョンは実際には
int __cdecl printf(const char *format, ...);
__cdecl
は、「呼び出し規則」を定義します。この呼び出し規則は、他のものとともに、引数の処理方法を記述します。この場合、argsがスタックにプッシュされ、呼び出しを行う関数によってスタックがクリーンアップされることを意味します。
_cdecl
の代替案の1つは__stdcall
ですが、他にもあります。 __stdcall
では、引数はスタックにプッシュされ、呼び出された関数によってクリーンアップされます。ただし、私の知る限りでは、__stdcall
関数が可変数の引数を受け入れることはできません。クリーンアップするスタックの量がわからないため、これは理にかなっています。
長い点と短い点は、__cdecl
関数の場合、クリーンアップは呼び出しを行うコードで実行されるため、必要な引数をいくつでも渡しても安全です。 __stdcall
関数に渡した引数が多すぎると、スタックが破損します。これが発生する可能性のある例の1つは、プロトタイプが間違っていた場合です。
呼び出し規約の詳細については、Wikipedia here を参照してください。
オンラインCドラフト標準(n1256) 、セクション7.19.6.1、パラグラフ2:
Fprintf関数は、後続の引数を出力用に変換する方法を指定するformatが指す文字列の制御下で、streamが指すストリームに出力を書き込みます。形式の引数が不十分な場合の動作は未定義です。 引数が残っている間に形式が使い果たされると、超過した引数は(いつものように)評価されますが、それ以外の場合は無視されます。 fprintf関数は、フォーマット文字列の終わりに到達すると戻ります。
他のすべての*printf()
関数の動作は、vprintf()
(明らかに)を除いて、余分な引数と同じです。
すべての引数はスタックにプッシュされ、スタックフレームが削除されると削除されます。この動作は、特定のプロセッサから独立しています。 (私は70年代に設計されたスタックのないメインフレームのみを覚えています)したがって、はい、2番目の例は失敗しません。
printf
は、任意の数の引数を受け入れるように設計されています。次に、printfはフォーマット指定子(最初の引数)を読み取り、必要に応じて引数リストから引数を引き出します。これがクラッシュする引数が少なすぎる理由です。コードは、存在しない引数の使用、存在しないメモリへのアクセス、またはその他の悪いことを単に開始します。しかし、引数が多すぎると、余分な引数は単に無視されます。形式指定子は、渡されたよりも少ない引数を使用します。