他の人が指摘したように、動作は未定義です (そのため、何かが起こる可能性があります)。
ただし、3 つの「典型的な」動作を見てみましょう。引数を渡す 3 つの一般的な方法は次のとおりです。
Intel x86 システムでは、ほとんどの場合、最初の方法が使用されます (ただし、2 番目または 3 番目の方法が使用される場合もあります)。MIPS ベースのプロセッサは、主に 2 番目を使用します。
システムが 1 つ以上のスタックを使用する場合、通常の呼び出し方法は次のとおりです。
- 呼び出し元 ( を呼び出す OS 提供のルーチン
main) で、通常は右から左、つまり逆の順序で引数をプッシュします。スタック プッシュは通常 (常にではありません) *--sp = value;C のように見え、スタック ポインターは上位アドレスから下降します。
- ターゲット関数を呼び出します (
main)
- ターゲット関数で、「スタック」または「パラメーター スタック」または「現在のスレッド スタック」またはシステムが使用するものからパラメーターを取得します。それらは逆の順序でプッシュされたため、 、 などのアドレスにあります
sp[0]。sp[1]呼び出しメカニズムがパラメーター受け渡しメカニズムと同じスタックを使用する場合、インデックスは 1 または 2 またはそれ以上 (sp[2]最初の引数であり、インスタンスでありsp[3]、2 番目である)。
この場合、argcはおそらく正しく出力されますがargv、発信者がプッシュしたものを誤って解釈し、奇妙な外観のint. 基礎となるシステムが十分に洗練されている (型をチェックしている) 場合、呼び出し元が type の値をプッシュしたchar **が、あなたが type の値にアクセスしていることを検出し、int何らかの実行時エラーが発生する可能性があります。ほとんどのシステムは単純に、タイプチェックをスキップして、できるだけ早く間違った答えを返すことを好みます。したがって、奇妙に見える が得られますが、int実際には、呼び出し元が渡そうとした実際のポインター値に基づいています (少なくとも部分的には以下を参照)。
システムが汎用レジスタを使用する場合 (スタックを使用する代わりに、またはスタックを使用する前に、GPR を使用するシステムは、多くのパラメーターを使用する場合にスタックにフォールバックすることが多く、すべての可変関数 (つまり、機能を使用するもの) にそれらを使用する場合があり<stdarg.h>ます)。呼び出しメソッドは次のようになります。
- 呼び出し側で、引数 (
int argcvalue とchar **argvvalue) を最初の 2 つの引数レジスタに移動します (たとえば、SPARC では%o0and 、MIPS では and ) 。%o1$a0$a1
- ターゲット関数を呼び出す
- ターゲット関数で、引数レジスタの値にアクセスします
この場合、コードは通常、スタックベースのシステムと同じように動作します。レジスタ内の引数はメモリ内の引数よりも必要な CPU サイクルが少ない傾向があるため、実行速度が向上します。(これが、一部の Intel コンパイラがレジスタで 1 つまたは 2 つの引数を渡す場合がある理由です。)
ただし、システムが特殊目的のレジスタを使用している場合は、明らかに新しい動作が得られます。浮動小数点値がレジスターに格納されるとしましょうf(一部の SPARC システムに当てはまります。x86 には代わりに MMX および SSE レジスターがあります)。ポインター値はaレジスターに入れられます (680x0 CPU のように)。整数値はdレジスタに格納されます (680x0 ですが、実際にはほとんどの 680x0 システムは単に「スタック」を使用しますが、レジスタを使用するシステムがあると仮定しましょう)。今回、 Thing 呼び出しmainは 1 つの整数argcと 1 つのポインターを渡す必要があるため、次argvのようにします。
- 整数引数
argcをデータ レジスタに移動するd0
- ポインタ引数
argvをポインタ レジスタに移動するa0
- 電話
main
では、コンパイラに 2 つの整数main()引数を期待するように指示しました。これらの引数はそれぞれレジスタおよびに到着します。CPUレジスタには何がありますか? 誰が知っていますか、呼び出したものは呼び出しの直前にそれを設定しませんでした. 最後に何らかの値を埋め込んだ人から、それが持っている値は何でもあります。register にあるため、値は意図した に関連付けられなくなりました。d0d1d1mainargva0
スタックまたは GPR ベースの呼び出しシステムを使用している場合でも、考慮すべき点が他にもいくつかあります。
- ポインターが 64 ビットで、プレーンな
ints が 32 ビットしかない場合はどうなるでしょうか? この場合、呼び出し元は 64 ビット値をプッシュするか、64 ビット値をパラメータ レジスタに書き込みます。しかしmain、32ビットしか見えません。実際に与えられたものの半分が表示されます。
- ポインタが 32 ビットでプレーンな
ints が 64 ビットの場合はどうなるでしょうか? 確かに、これは通常とは異なる実装ですが、ここでは、32 しか提供されていない値の 64 ビットすべてを見ることになります。「余分な」32 ビットはすべてゼロである可能性があります (これは、GPR のパラメーターでは一般的です)、またはの呼び出し元が registerに入力されd1たときにregister を検査する場合と同様に、32 ビットの無関係な値である可能性があります。maina0
- そしてもちろん、32 ビットと 64 ビットだけが可能なサイズであるということはありません。IBM AS/400 システムでは、ポインターの長さはなんと 128 ビット (16 バイトのタグ付きポインター) であり、実行時に広範な型チェックが行われます。これらのマシンは、単に高速であるだけでなく、コードが正しいことを確認するために動作します。
もう1つの注目すべき可能性があります。同様の C++ コードを ( 以外の関数でmain) ビルドすると、通常、リンクに失敗します。その理由は、C++ コンパイラがオーバーロードされた関数を処理するために「名前マングリング」と呼ばれる手法を使用することが多いためです。1つと 1 つの引数fを取り、返すという名前の関数は、リンク時のシンボル を生成します。2 つの を受け取って返すという名前の関数は、代わりにリンク時のシンボルを生成します。これを行う C コンパイラは見たことがありませんが、可能でした。この場合、コンパイラはリンク時に、プログラムが定義されているかどうかをチェックします。intchar **intZ1fiPPCfintintZ1fiiZ4mainippC—<code>int main(int, char **)—そうであれば、それらの引数を提供する呼び出し元にリンクします。またはZ4mainv—<code>int main(void)—をチェックし、その場合、引数を提供しない呼び出し元のリンクをチェックします。どちらの関数も見つからない場合、リンカーは、間違った関数を記述したことを検出しmain、実行可能ファイルをまったく生成しない可能性があります!