main関数はいくつかの方法で記述できます。
int main()
int main(int argc,char *argv[])
int main(int argc,char *argv[],char * environment)
ランタイムCRT関数が、どのメインを呼び出す必要があるかをどのように認識するか。ここで注意してください、私はUnicodeがサポートされているかどうかについて尋ねていません。
受け入れられた答えは正しくありません。CRTには、main()宣言の種類を認識するための特別なコードはありません。
これは、cdecl呼び出し規約のために機能します。これは、引数がスタック上で右から左にプッシュされ、呼び出し元が呼び出し後にスタックをクリーンアップすることを指定します。したがって、CRTは単にすべての引数をmain()に渡し、main()が戻ったときにそれらを再度ポップします。あなたがする必要がある唯一のことはあなたのmain()関数宣言で正しい順序で引数を指定することです。argcパラメーターを最初に指定する必要があります。これは、スタックの最上位にあるパラメーターです。argvは2番目である必要があります。引数を省略しても、後続の引数もすべて省略している限り、違いはありません。
これがprintf()関数が機能する理由でもあり、引数の数は可変です。既知の位置に1つの引数があり、最初の引数。
一般に、コンパイラー/リンカーは、main
使用している特定の形式を認識し、それをシステムのスタートアップ関数からCまたはC++main
関数に適合させるためのコードを含める必要があります。
ハンスが彼の答えで説明している方法を使用して、特定のプラットフォーム上の特定のコンパイラがこれを行わなくても逃げることができるのは事実です。ただし、すべてのプラットフォームがスタックを使用してパラメーターを渡すわけではなく、互換性のないパラメーターリストを持つ適合CおよびC++実装を作成することは可能です。このような場合、コンパイラ/リンカはmain
、呼び出す形式を決定する必要があります。
うーん。以前に受け入れられた回答が正しくないことを示す、現在受け入れられている回答自体が正しくないようです。この質問のタグは、CだけでなくC ++にも適用されることを示しているため、C99ではなくC++仕様に固執します。他のすべての説明や議論に関係なく、この質問に対する主な答えは、「main()は実装定義の方法で特別に扱われる」ということです。デビッドの答えはハンスの答えよりも技術的に正しいと思いますが、詳しく説明します。
main()関数は面白い関数であり、コンパイラとリンカによって処理され、他の関数とは一致しない動作をします。ハンスは、main()のさまざまな署名を認識する特別なコードがCRTにないことは正しいですが、「cdecl呼び出し規約のために機能する」という彼の主張は、特定のプラットフォーム、特にVisualStudioにのみ適用されます。main()のさまざまなシグニチャを認識するための特別なコードがCRTにない本当の理由は、そうする必要がないということです。そして、それは一種の分裂のようなものですが、リンク時にスタートアップコードをmain()に結び付けるのが仕事であるリンカーであり、スタートアップ時のCRTの仕事ではありません。
main()関数の処理方法の多くは、C ++仕様に従って、実装によって定義されます(セクション3.6「開始と終了」を参照)。ほとんどの実装のコンパイラは、main()をextern“ C”リンケージに似たもので暗黙的に処理し、main()を装飾されていない状態のままにして、関数プロトタイプに関係なく、リンカーシンボルが同じになるようにします。あるいは、実装のリンカは、シンボルテーブルをスキャンして、装飾された名前が「[int | void] main(...)」の形式に解決されるものを探すのに十分賢い場合があります(戻り型としてのvoidは仕様自体がmain()の戻り型は'int'でなければならないと言っているので、それ自体は実装固有のものです。使用可能なシンボルでそのような関数が見つかると、リンカーは、スタートアップコードが「main()」を参照している関数を使用するだけで済みます。したがって、正確なシンボル名は必ずしも特定のものと一致する必要はありません。リンカが検索するバリエーションを知っているか、コンパイラがすべてのバリエーションに同じシンボル名を付与している限り、wmain()またはその他の場合もあります。
また、仕様ではmain()がオーバーロードされない可能性があると規定されているため、リンカーはさまざまな形式のmain()の複数のユーザー実装間で「選択」する必要がないことに注意してください。複数が見つかった場合は、引数リストが一致していなくても、重複シンボルエラー(または他の同様のエラー)です。そして、すべての実装は両方を「許可する」必要がありますが
int main() { /* ... */ }
と
int main(int argc, char* argv[]) { /* ... */ }
また、環境文字列配列ポインタを含む表示バージョンや、特定の実装で意味のあるその他のバリエーションなど、他の引数リストを許可することも許可されています。
ハンスが示すように、Visual Studioコンパイラのcdecl呼び出し規約(および他の多くのコンパイラの呼び出し規約)は、呼び出し元が呼び出し環境(つまり、スタック、ABI定義レジスタ、または2つの組み合わせ)を設定できるフレームワークを提供します。可変数の引数を渡すことができ、呼び出し先が戻ったときに、呼び出し元がクリーンアップの責任を負います(スタックから使用済みの引数スペースをポップするか、レジスターの場合は、クリーンアップのために何もする必要はありません)。このセットアップは、必要以上のパラメーターを渡すスタートアップコードに適しています。また、多くのプラットフォームでさまざまな形式の処理を行う場合と同様に、ユーザーのmain()実装は、これらの引数を自由に使用することも使用しないこともできます。 main()あなたはあなたの質問にリストします。でも、これは、コンパイラとリンカがこの目標を達成できる唯一の方法ではありません。代わりに、リンカは、main()の定義に基づいて、スタートアップコードのさまざまなバージョンから選択できます。そうすることで、cdecl呼び出し規約クリーンアップモデルでは不可能なさまざまなmain()引数リストが可能になります。そして、それらはすべて実装定義であるため、コンパイラとリンカが少なくとも上記の2つの組み合わせをサポートしている限り、C ++仕様に従って合法です(int main()
およびint main(int, char**)
)。
C 99標準(5.1.2.2.1プログラムの起動)では、実装はmain()関数のプロトタイプを強制せず、プログラムはそれを次のいずれかとして定義できると述べています。
1)int main(void);
2)int main(int argc、char * argv []);
または2)と意味的に同等の方法で、例えば
2')int main(int argc、char ** argv);
または他の実装で定義された方法で。プロトタイプは次のことを義務付けていません。
3)int main(int argc、char * argv []、char * envp []);
プロトタイプはコンパイルする必要があるため、そのプロトタイプはコンパイルする必要がありますが、意図した動作をします。3)他のコンパイラの中でもGCCとMicrosoftCによってサポートされています。(注:質問者の3番目のプロトタイプには、偶然であろうと、他のコンパイラーがあるためであろうと、char *envp[]ではなくchar*envpがあります)。
GCCとMicrosoftCはどちらも、必要に応じて、任意のプロトタイプを使用してmain()をコンパイルします。実際に指定したプロトタイプを解析し、アセンブリ言語を生成して、引数がある場合は正しい方法で使用します。したがって、たとえば、それぞれがプログラムに期待される動作を生成します。
#include <stdio.h>
void main(double d, char c)
{
printf("%lf\n",d);
putchar(c);
}
文字列の配列を介さずに、doubleとcharをプログラムに直接渡す方法を見つけることができれば。
これらの観察結果は、実験プログラムのアセンブリ言語リストを有効にすることで確認できます。
コンパイラの標準CRTで生成されたmain()の実装を呼び出す方法の問題は、main()をコンパイラに定義する方法の問題とは異なります。
GCCとMSCの両方で、main()は任意の方法で定義できます。ただし、いずれの場合も、実装の標準CRTであるAFIKは、3)よりもmain()への引数の受け渡しのみをサポートします。したがって、1)-2')も、過剰な引数を無視することで期待される動作を示します。また、独自の非標準ランタイムを提供する以外に、他のオプションはありません。
Hans Passantの答えは、argcがprintf()の最初の引数と同じ方法で、後続の引数をいくつ消費するかを関数に指示することを示唆するという点で、偶然にも誤解を招くようです。argcが存在する場合は、2番目の引数argvとして渡された配列内の要素の数のみを示します。main()に渡される引数の数を示すものではありません。GCCとMSCはどちらも、作成したプロトタイプを解析することで、どの引数が期待されるかを理解します。基本的に、可変数の引数を取るように定義されているprintf()などの関数を除く関数でコンパイラーが行うことです。
main()は可変数の引数を取りません。定義で指定した引数を取り、通常のコンパイラの標準CRTはそれらを(int、char * []、char * [])と見なします。
まず、 main
関数はGCCで特別に扱われます(たとえば、GCC 4.7のソースツリーのmain_identifier_node
ファイル内)gcc/c-family/c-common.c
また、C11およびC ++ 11標準には、特定の表現と仕様があります。
次に、C呼び出しのABI規則は通常、余分な引数があまり害を及ぼさないようにするためのものです。
したがって、言語仕様とコンパイラの両方に、の「オーバーロード」に関する特定の事項があるかのように考えることができますmain
。
main
それは普通の機能ではないかもしれないとさえ思います。私は、標準のいくつかの単語(私が今持っていない)は、例えば、そのアドレスを取ることを禁じたり、に繰り返したりすることを禁じていると理解されるかもしれないと信じていますmain
。
実際には、によってリンクされたファイルにmain
コンパイルされたアセンブリコードによって呼び出されます。何が起こっているのかをもっと理解するために使用します。crt*.o
gcc
gcc -v