1. ホーム
  2. c++

[解決済み] C言語のmain()メソッドはどのように動作するのですか?

2022-03-16 21:09:06

質問

mainメソッドの書き方として、2種類のシグネチャがあることは知っています。

int main()
{
   //Code
}

また、コマンドライン引数を扱う場合は、次のように記述します。

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

C++ メソッドをオーバーロードできることは知っていますが、その中で C の2つの異なるシグネチャをコンパイラはどのように処理するのでしょうか? main 関数を使用できますか?

どのように解決するのですか?

C言語の機能の中には、たまたまうまくいったハッキングから始まったものもある。

mainの複数署名や可変長引数リストもその一つです。

プログラマーは、関数に余分な引数を渡しても、与えられたコンパイラで何も悪いことが起きないことに気づきました。

というような呼び出し規約がある場合です。

  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 は引数なしの関数で、この場合、スタックにプッシュされたアイテムは無視されます。もしそれが2つの引数を持つ関数なら、それは argcargv をスタックの一番上の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;