他の人が指摘したように、動作は未定義です (そのため、何かが起こる可能性があります)。
ただし、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 argc
value とchar **argv
value) を最初の 2 つの引数レジスタに移動します (たとえば、SPARC では%o0
and 、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 にあるため、値は意図した に関連付けられなくなりました。d0
d1
d1
main
argv
a0
スタックまたは GPR ベースの呼び出しシステムを使用している場合でも、考慮すべき点が他にもいくつかあります。
- ポインターが 64 ビットで、プレーンな
int
s が 32 ビットしかない場合はどうなるでしょうか? この場合、呼び出し元は 64 ビット値をプッシュするか、64 ビット値をパラメータ レジスタに書き込みます。しかしmain
、32ビットしか見えません。実際に与えられたものの半分が表示されます。
- ポインタが 32 ビットでプレーンな
int
s が 64 ビットの場合はどうなるでしょうか? 確かに、これは通常とは異なる実装ですが、ここでは、32 しか提供されていない値の 64 ビットすべてを見ることになります。「余分な」32 ビットはすべてゼロである可能性があります (これは、GPR のパラメーターでは一般的です)、またはの呼び出し元が registerに入力されd1
たときにregister を検査する場合と同様に、32 ビットの無関係な値である可能性があります。main
a0
- そしてもちろん、32 ビットと 64 ビットだけが可能なサイズであるということはありません。IBM AS/400 システムでは、ポインターの長さはなんと 128 ビット (16 バイトのタグ付きポインター) であり、実行時に広範な型チェックが行われます。これらのマシンは、単に高速であるだけでなく、コードが正しいことを確認するために動作します。
もう1つの注目すべき可能性があります。同様の C++ コードを ( 以外の関数でmain
) ビルドすると、通常、リンクに失敗します。その理由は、C++ コンパイラがオーバーロードされた関数を処理するために「名前マングリング」と呼ばれる手法を使用することが多いためです。1つと 1 つの引数f
を取り、返すという名前の関数は、リンク時のシンボル を生成します。2 つの を受け取って返すという名前の関数は、代わりにリンク時のシンボルを生成します。これを行う C コンパイラは見たことがありませんが、可能でした。この場合、コンパイラはリンク時に、プログラムが定義されているかどうかをチェックします。int
char **
int
Z1fiPPC
f
int
int
Z1fii
Z4mainippC
—<code>int main(int, char **)—そうであれば、それらの引数を提供する呼び出し元にリンクします。またはZ4mainv
—<code>int main(void)—をチェックし、その場合、引数を提供しない呼び出し元のリンクをチェックします。どちらの関数も見つからない場合、リンカーは、間違った関数を記述したことを検出しmain
、実行可能ファイルをまったく生成しない可能性があります!