main()メソッドはCでどのように機能しますか?


96

mainメソッドを記述するために2つの異なるシグネチャがあることを知っています-

int main()
{
   //Code
}

または、コマンドライン引数を処理するために、次のように記述します。

int main(int argc, char * argv[])
{
   //code
}

ではC++、私たちはメソッドをオーバーロードが、中にすることができます知っているCどのようにコンパイラがこれら二つの異なるシグネチャ取り扱うないmain機能を?


14
オーバーロードとは、同じプログラムに同じ名前の2つのメソッドがあることを指します。main単一のプログラムでC(または、実際には、そのような構造を持つほとんどすべての言語で)1つのメソッドしか持つことができません。
カイルストランド

12
Cにはメソッドがありません。機能があります。メソッドは、オブジェクト指向の「汎用」関数のバックエンド実装です。プログラムはいくつかのオブジェクト引数を指定して関数を呼び出し、オブジェクトシステムはその型に基づいてメソッド(またはメソッドのセット)を選択します。自分でシミュレートしない限り、Cにはこれはありません。
Kaz

4
特にプログラムエントリポイントについての深い議論mainには、John R. Levinesのクラシックブック「Linkers&Loaders」をお勧めします。
Andreas Spindler 2013年

1
Cでは、最初の形式はint main(void)、ありませんint main()(私は拒否コンパイラ見たことがないのにint main()フォームを)。
キース・トンプソン

1
@harper:()フォームは陳腐化しており、許可されているかどうかは明確ではありませんmain(実装が許可されたフォームとして具体的に文書化していない限り)。C標準(5.1.2.2.1プログラムの起動を参照)は()フォームに言及していません。これはフォームとまったく同じではありません()。詳細はこのコメントには長すぎます。
キーストンプソン

回答:


132

C言語の機能の一部は、たまたま機能したハックとして始まりました。

mainの複数の署名、および可変長引数リストは、それらの機能の1つです。

プログラマーは関数に追加の引数を渡すことができることに気づき、コンパイラーを使用しても問題は発生しません。

これは、呼び出し規約が次のような場合に当てはまります。

  1. 呼び出し元の関数は引数をクリーンアップします。
  2. 左端の引数はスタックの最上部またはスタックフレームのベースに近いため、偽の引数がアドレス指定を無効にすることはありません。

これらのルールに従う呼び出し規約の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引数なしの関数にすることができます。その場合、スタックにプッシュされた項目は気づかれません。それは二つの引数の関数の場合は、それが見つかったargcargv2つの一番上のスタック項目として。それが環境ポインター(一般的な拡張)を持つプラットフォーム固有の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;


34

mainC ++でもオーバーロードはありません。メイン関数はプログラムのエントリポイントであり、定義は1つだけ存在する必要があります。

標準Cの場合

ホストされた環境(通常の環境)の場合、C99標準は次のように述べています。

5.1.2.2.1プログラムの起動

プログラムの起動時に呼び出される関数の名前はmainです。実装は、この関数のプロトタイプを宣言していません。これは、戻り値の型intとパラメーターなしで定義されます。

int main(void) { /* ... */ }

または2つのパラメーターを使用して(ここではargcand と呼ばれます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実装 main関数を事前定義してはなりませんこの関数はオーバーロードしないでください。戻り型はint型ですが、それ以外の場合はその型は実装定義です。すべての実装は、以下のメインの定義の両方を許可するものとします。

int main() { /* ... */ }

そして

int main(int argc, char* argv[]) { /* ... */ }

C ++標準では、「[メイン関数]の戻り値の型はint型である必要がありますが、それ以外の場合はその型は実装で定義されています」と明示されており、C標準と同じ2つの署名が必要です。

ホストされた環境(また、CライブラリをサポートしているAC環境) -オペレーティングシステムを呼び出しますmain

ホストされていない環境(組み込みアプリケーション向けの環境)では、次のようなプリプロセッサディレクティブを使用して、プログラムのエントリポイント(または出口)をいつでも変更できます。

#pragma startup [priority]
#pragma exit [priority]

優先順位はオプションの整数です。

プラグマスタートアップはメインの前に(優先度的に)関数を実行し、プラグマ出口はメイン関数の後に関数を実行します。起動ディレクティブが複数ある場合は、優先順位によってどちらが最初に実行されるかが決まります。


4
私はそうは思いません。この答えは、コンパイラが実際に状況を処理する方法の質問に実際に答えます。@Kazによって与えられた答えは、私の意見ではより多くの洞察を与えます。
Tilman Vogel

4
この回答は@Kazの質問よりも適切に回答すると思います。元の質問は、オペレーターのオーバーロードが発生しているという印象の下にあり、この回答は、オーバーロードの解決策ではなく、コンパイラーが2つの異なるシグニチャーを受け入れることを示すことで解決します。コンパイラの詳細は興味深いですが、質問に答える必要はありません。
Waleed Khan

1
自立型環境(「非ホスト型」)の場合、いくつかの#pragma以外にも多くのことが行われています。ハードウェアからのリセット割り込みがあり、それが実際にプログラムの始まりです。そこから、すべての基本的なセットアップが実行されます。セットアップスタック、レジスタ、MMU、メモリマッピングなど。次に、NVMから静的ストレージ変数への初期値のコピーダウン(.dataセグメント)が発生し、すべての "ゼロアウト"ゼロに設定する必要がある静的ストレージ変数(.bssセグメント)。C ++では、静的ストレージ期間を持つオブジェクトのコンストラクターが呼び出されます。そして、すべての処理が完了すると、mainが呼び出されます。
ランディン2013年

8

オーバーロードの必要はありません。はい、2つのバージョンがありますが、一度に使用できるのは1つだけです。


5

これは、CおよびC ++言語の奇妙な非対称性と特別な規則の1つです。

私の意見では、それは歴史的な理由でのみ存在し、その背後に本当の真剣な論理はありません。main他の理由でも特別であることに注意してください(たとえばmain、C ++では再帰的ではなく、そのアドレスを取得できません。C99/ C ++では、最後のreturnステートメントを省略できます)。

C ++でもオーバーロードではないことにも注意してください...プログラムには最初の形式か2番目の形式があります。両方を持つことはできません。


returnC のステートメントも省略できます(C99以降)。
dreamlax 2013年

Cでは、main()そのアドレスを呼び出して取得できます。C ++では、Cにはない制限が適用されます。
ジョナサンレフラー2013年

@JonathanLeffler:正解です。戻り値を省略する可能性に加えて、C99仕様で見つけたmainの唯一の面白い点は、標準がIIUCと表現されているためargc、再帰時に負の値を渡すことができないことです(5.1.2.2.1では制限を指定していません)argcそしてargv唯一の最初の呼び出しに適用されますmain)。
6502 2013年

4

珍しいのmainは、複数の方法で定義できることではなく、2つの異なる方法のいずれかでしか定義できないことです。

mainユーザー定義関数です。実装はそれのプロトタイプを宣言しません。

fooor についても同じことが言えbarますが、好きな方法でこれらの名前で関数を定義できます。

違いはmain、独自のコードだけでなく、実装(ランタイム環境)によって呼び出されることです。実装は通常のC関数呼び出しのセマンティクスに限定されないため、いくつかのバリエーションを処理できます(そして処理する必要があります)-しかし、無限に多くの可能性を処理する必要はありません。このint main(int argc, char *argv[])フォームではコマンドライン引数を使用できますint main(void)。Cまたはint main()C ++では、コマンドライン引数を処理する必要のない単純なプログラムに便利です。

コンパイラがこれを処理する方法については、実装に依存します。ほとんどのシステムにはおそらく2つの形式を効果的に互換にする呼び出し規約があり、mainパラメータなしで定義に渡される引数は静かに無視されます。そうでなければ、コンパイラやリンカがmain特別に扱うことは難しくありません。システムでどのように機能するか知りたい場合は、いくつかのアセンブリリストを参照してください

また、CおよびC ++の多くのことと同様に、詳細は主に、言語の設計者とその前任者が行った歴史と恣意的な決定の結果です。

CとC ++はどちらも、他の実装定義の定義を許可mainしますが、それらを使用する正当な理由はほとんどありません。また、自立型の実装(OSのない組み込みシステムなど)の場合、プログラムのエントリポイントは実装定義であり、必ずしも呼び出される必要はありませんmain


3

mainリンカによって決定された開始アドレスのためだけの名前でmainデフォルト名です。プログラム内のすべての関数名は、関数が開始する開始アドレスです。

関数の引数はスタックにプッシュ/ポップされるため、関数に指定された引数がない場合、スタックにプッシュ/ポップされる引数はありません。これは、mainが引数の有無に関係なく機能する方法です。


2

さて、同じ関数main()の2つの異なるシグネチャは、必要なときにのみ表示されます。つまり、プログラムがコードの実際の処理の前にデータを必要とする場合、使用してそれらを渡すことができます-

    int main(int argc, char * argv[])
    {
       //code
    }

ここで、変数argcは渡されたデータの数を格納し、argvはコンソールから渡された値を指すcharへのポインターの配列です。それ以外の場合は常に一緒に行くのが良いです

    int main()
    {
       //Code
    }

ただし、プログラム内にmain()が1つだけ存在する可能性があります。これは、プログラムから実行を開始する唯一のポイントであり、1つを超えることはできないためです。(その価値を願っています)


2

同様の質問が以前に尋ねられました:(実際の関数定義と比較して)パラメーターのない関数がコンパイルされるのはなぜですか?

上位の回答の1つは次のとおりです。

Cでは、任意の数の引数をfunc()渡すことができます。引数が必要ない場合は、次のように宣言する必要がありますfunc(void)

だから、私はそれがどのようmainに宣言されているかを推測します(「宣言された」という用語をに適用できる場合main)。実際、次のように書くことができます:

int main(int only_one_argument) {
    // code
}

コンパイルして実行します。


1
素晴らしい観察!mainまだ言及されていない問題があるため、リンカはをかなり許容しているようですmain。「Unix(Posix.1ではない)とMicrosoft Windows」が追加されchar **envp(DOSでも許可されていましたね)、Mac OS XとDarwinはさらに「任意のOS提供情報」のchar *ポインタを追加しています。ウィキペディア
usr2564301 2013年

0

一度に使用されるのは1つだけなので、これをオーバーライドする必要はありません。メイン関数には2つの異なるバージョンがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.