メインメソッドを記述するには2つの異なるシグネチャがあることを知っています-
int main()
{
//Code
}
またはコマンドライン引数を処理するために、次のように記述します-
int main(int argc, char * argv[])
{
//code
}
メソッドをオーバーロードできることはC++
わかっていC
ますが、コンパイラは関数のこれら 2 つの異なるシグネチャをどのように処理するのmain
でしょうか?
C 言語の機能のいくつかは、たまたま機能するハックとして始まりました。
メインの複数の署名と可変長の引数リストは、それらの機能の 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
引数のない関数にすることができます。その場合、スタックにプッシュされたアイテムは無視されます。引数が 2 つの関数の場合、最上位の 2 つのスタック項目としてargc
andが検出されます。argv
環境ポインター (一般的な拡張機能) を持つプラットフォーム固有の 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 つだけ存在する必要があります。
標準 C の場合
ホスト環境 (通常の環境) の場合、C99 標準では次のように規定されています。
5.1.2.2.1 プログラムの起動
プログラムの起動時に呼び出される関数の名前は
main
です。実装は、この関数のプロトタイプを宣言していません。戻り値の型をint
パラメータなしで定義する必要があります。int main(void) { /* ... */ }
または 2 つのパラメーター (ここでは
argc
およびと呼ばれますargv
が、宣言されている関数に対してローカルであるため、任意の名前を使用できます):int main(int argc, char *argv[]) { /* ... */ }
または同等のもの; 9)または他の実装定義の方法で。
9)したがって、
int
として定義された typedef 名に置き換えることがint
できargv
ますchar **argv
。
標準 C++ の場合:
3.6.1 メイン関数 [basic.start.main]
1 プログラムには、プログラムの指定された開始である main と呼ばれるグローバル関数が含まれます。[...]
2 実装は、メイン関数を事前に定義してはなりません。この関数はオーバーロードされません。型 int の戻り値の型を持つ必要がありますが、それ以外の場合、その型は実装定義です。すべての実装は、main の次の定義の両方を許可する必要があります。
int main() { /* ... */ }
と
int main(int argc, char* argv[]) { /* ... */ }
C++ 標準では、「それ [メイン関数] は int 型の戻り値の型を持ちますが、それ以外の場合、その型は実装定義です」と明示的に述べており、C 標準と同じ 2 つの署名が必要です。
ホスト環境(C ライブラリもサポートする AC 環境) では、オペレーティング システムが を呼び出しますmain
。
ホストされていない環境(組み込みアプリケーション向けの環境) では、次のようなプリプロセッサ ディレクティブを使用して、プログラムのエントリ ポイント (または出口) をいつでも変更できます。
#pragma startup [priority]
#pragma exit [priority]
優先度はオプションの整数です。
pragma startup はメイン関数の前に (優先度に従って) 関数を実行し、pragma exit はメイン関数の後に関数を実行します。複数の起動ディレクティブがある場合、優先度によってどれが最初に実行されるかが決まります。
オーバーロードする必要はありません。はい、2 つのバージョンがありますが、一度に使用できるのは 1 つだけです。
これは、C および C++ 言語の奇妙な非対称性と特殊規則の 1 つです。
私の意見では、それは歴史的な理由からのみ存在し、その背後に真の深刻な論理はありません. は他の理由からも特別であることに注意してくださいmain
(たとえばmain
、C++ では再帰的でなく、そのアドレスを取得できず、C99/C++ では最終return
ステートメントを省略できます)。
また、C++ でもオーバーロードではないことに注意してください。プログラムには最初の形式または 2 番目の形式があります。両方を持つことはできません。
珍しいのmain
は、複数の方法で定義できることではなく、2 つの異なる方法のいずれかでしか定義できないことです。
main
ユーザー定義関数です。実装はそのプロトタイプを宣言しません。
foo
またはについても同じことが言えbar
ますが、好きなようにこれらの名前で関数を定義できます。
違いはmain
、独自のコードだけでなく、実装 (ランタイム環境) によって呼び出されることです。実装は通常の C 関数呼び出しのセマンティクスに限定されないため、いくつかのバリエーションを処理できます (また、処理する必要があります)。ただし、無限に多くの可能性を処理する必要はありません。このint main(int argc, char *argv[])
形式ではコマンド ライン引数を使用できますint main(void)
。C またはint main()
C++ では、コマンド ライン引数を処理する必要のない単純なプログラムに便利です。
コンパイラがこれをどのように処理するかについては、実装によって異なります。ほとんどのシステムには、おそらく 2 つの形式を実質的に互換性のあるものにする呼び出し規則があり、パラメータなしで defined に渡された引数main
は静かに無視されます。main
そうでなければ、コンパイラやリンカが特別に扱うことは難しくありません。システムでどのように機能するか興味がある場合は、いくつかのアセンブリ リストを参照してください。
そして、C や C++ の多くのことと同様に、詳細は主に歴史と、言語の設計者とその前任者によって行われた恣意的な決定の結果です。
C と C++ の両方がmain
-- の他の実装定義の定義を許可することに注意してください。ただし、それらを使用する正当な理由はほとんどありません。また、独立した実装(OS を持たない組み込みシステムなど) の場合、プログラムのエントリ ポイントは実装定義であり、必ずしも呼び出されるとは限りませんmain
。
はmain
リンカによって決定される開始アドレスの名前でmain
あり、デフォルト名は です。プログラム内のすべての関数名は、関数が開始する開始アドレスです。
関数の引数はスタックにプッシュ/スタックからポップされるため、関数に引数が指定されていない場合、スタックにプッシュ/ポップされる引数はありません。これが、 main が引数の有無にかかわらず機能する方法です。
これをオーバーライドする必要はありません.一度に1つしか使用されないためです.はい、メイン関数には2つの異なるバージョンがあります