C言語の機能の一部は、たまたま機能したハックとして始まりました。
mainの複数の署名、および可変長引数リストは、それらの機能の1つです。
プログラマーは関数に追加の引数を渡すことができることに気づき、コンパイラーを使用しても問題は発生しません。
これは、呼び出し規約が次のような場合に当てはまります。
- 呼び出し元の関数は引数をクリーンアップします。
- 左端の引数はスタックの最上部またはスタックフレームのベースに近いため、偽の引数がアドレス指定を無効にすることはありません。
これらのルールに従う呼び出し規約の1つのセットは、スタックベースのパラメーターの受け渡しであり、これにより、呼び出し元は引数をポップし、右から左にプッシュされます。
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
このタイプの呼び出し規約が当てはまるコンパイラーでは、2種類のmain
、または追加の種類をサポートするために特別なことを行う必要はありません。main
引数なしの関数にすることができます。その場合、スタックにプッシュされた項目は気づかれません。それは二つの引数の関数の場合は、それが見つかったargc
とargv
2つの一番上のスタック項目として。それが環境ポインター(一般的な拡張)を持つプラットフォーム固有の3つの引数のバリアントである場合、それも機能します。スタックの一番上から3番目の要素として3番目の引数が見つかります。
したがって、固定呼び出しはすべての場合に機能し、単一の固定起動モジュールをプログラムにリンクできます。そのモジュールはこれに似た関数としてCで書くことができます:
/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */
/* ... */
/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
つまり、この開始モジュールは常に3つの引数のmainを呼び出すだけです。mainが引数を取らない場合、またはのみのint, char **
場合、呼び出し規約により、引数を取らない場合と同様に、正常に機能します。
プログラムでこの種のことを行う場合、それは移植性がなく、ISO Cによる未定義の動作と見なされます。ある方法で関数を宣言して呼び出し、別の方法でそれを定義します。しかし、コンパイラーの起動トリックは移植可能である必要はありません。移植可能なプログラムの規則によって導かれるものではありません。
しかし、呼び出し規約がこのように機能しないようなものであると仮定します。その場合、コンパイラーはmain
特別に処理する必要があります。main
関数をコンパイルしていることに気づくと、たとえば3つの引数の呼び出しと互換性のあるコードを生成できます。
つまり、次のように記述します。
int main(void)
{
/* ... */
}
しかし、コンパイラはそれを見ると、基本的にコード変換を実行して、コンパイルする関数が次のようになるようにします。
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
名前__argc_ignore
が文字通り存在しないことを除いて。そのような名前はスコープに導入されず、未使用の引数に関する警告はありません。コード変換により、コンパイラーは3つの引数をクリーンアップする必要があることを認識している正しいリンケージでコードを発行します。
別の実装戦略は、コンパイラーまたはおそらくリンカーが__start
関数(またはそれが呼び出されるもの)をカスタム生成するか、少なくともいくつかのコンパイル済みの選択肢から1つを選択することです。サポートされているどの形式main
が使用されているかに関する情報をオブジェクトファイルに保存できます。リンカはこの情報を見main
て、プログラムの定義と互換性のある呼び出しを含む起動モジュールの正しいバージョンを選択できます。Cの実装では通常、サポートされる形式が少数しかないmain
ため、このアプローチは実現可能です。
C99言語のコンパイラは、main
関数がreturn
ステートメントなしで終了した場合の動作return 0
が実行されたかのように動作するハックをサポートするために、常にある程度特別に扱う必要があります。これも、コード変換によって処理できます。コンパイラは、呼び出された関数main
がコンパイルされていることを認識します。次に、体の末端に到達できるかどうかを確認します。もしそうなら、それは挿入しますreturn 0;
main
単一のプログラムでC
(または、実際には、そのような構造を持つほとんどすべての言語で)1つのメソッドしか持つことができません。