C/C++ では、メイン関数は型のパラメーターを受け取りますchar*
。
int main(int argc, char* argv[]){
return 0;
}
argv
の配列でありchar*
、文字列を指します。これらの文字列はどこにありますか? それらはヒープ、スタック、または他の場所にありますか?
C/C++ では、メイン関数は型のパラメーターを受け取りますchar*
。
int main(int argc, char* argv[]){
return 0;
}
argv
の配列でありchar*
、文字列を指します。これらの文字列はどこにありますか? それらはヒープ、スタック、または他の場所にありますか?
それらはコンパイラの魔法であり、実装に依存します。
C標準(n1256)の内容は次のとおりです。
5.1.2.2.1プログラムの起動
... 2宣言されている場合、 main
関数 のパラメータは次の制約に従う必要があります。
- argcの値は非負でなければなりません。
- argv[argc]はnullポインタでなければなりません。
- argcの値がゼロより大きい場合、配列メンバーargv[0]から argv[argc-1]までは、プログラムの起動前にホスト環境によって実装定義の値が与えられた文字列へのポインターを含むものとします。目的は、ホストされた環境の他の場所からプログラムを起動する前に決定された情報をプログラムに提供することです。ホスト環境が大文字と小文字の両方の文字を含む文字列を提供できない場合、実装は文字列が小文字で受信されることを保証する必要があります。
- argcの値がゼロより大きい場合、argv[0]が指す文字列はプログラム名 を表します。プログラム名がホスト環境から利用できない場合、 argv[0][0]はヌル文字になります。argcの値が1より大きい場合、argv[1]からargv[argc-1]が指す文字列はプログラムパラメータ を表します。
- パラメータargcとargv 、およびargv配列が指す文字列は、プログラムによって変更可能であり、プログラムの起動から終了までの間に最後に保存された値を保持する必要があります。
最後の箇条書きは、文字列値が格納されている最も興味深いwrtです。ヒープやスタックは指定しませんが、文字列が書き込み可能で静的な範囲を持っている必要があります。これにより、文字列の内容を配置できる場所に制限が生じます。他の人が言っているように、正確な詳細は実装によって異なります。
実際には、コンパイラ依存とオペレーティング システム依存の組み合わせです。 main()
は他の C 関数と同様の関数であるため、2 つのパラメーターの場所はargc
プラットフォームargv
上のコンパイラの標準に従います。たとえば、x86 を対象とするほとんどの C コンパイラでは、戻りアドレスと保存されたベース ポインターのすぐ上のスタックに配置されます (スタックは下に向かって成長します)。x86_64 では、パラメーターはレジスターで渡されるためargc
、 in%edi
およびin にargv
なります%rsi
。次に、コンパイラによって生成されたメイン関数のコードがそれらをスタックにコピーし、それが後の参照ポイントになります。これは、レジスタを からの関数呼び出しに使用できるようにするためですmain
。
char*
argv が指す sのブロックと実際の文字列はどこにでもある可能性があります。これらはオペレーティング システムで定義された場所で開始され、リンカーが生成するプリアンブル コードによってスタックまたは別の場所にコピーされる場合があります。コードexec()
とリンカによって生成されたアセンブラのプリアンブルを調べて確認する必要があります。
この質問に対する答えは、コンパイラに依存します。これは、C 標準では扱われないことを意味するため、誰でも好きなように実装できます。オペレーティング システムには、プロセスを開始して終了するための一般的に受け入れられている標準的な方法がないため、これは正常です。
単純な理由のないシナリオを想像してみましょう。
プロセスは、コマンド ラインに記述された引数を何らかのメカニズムで受け取ります。argc は、コンパイラがプログラムのプロセス (ランタイムの一部) のエントリ ポイントとして配置するブートストラップ関数によってスタックにプッシュされる単なる int です。実際の値はオペレーティング システムから取得され、ヒープのメモリ ブロックに書き込むことができます。次に、argv ベクトルが構築され、その最初の位置へのアドレスもスタックにプッシュされます。
次に、プログラマが提供する必要がある関数 main() が呼び出され、その戻り値が後で (ほぼ中間で) 使用できるように保存されます。ヒープ内の構造が解放され、main 用に取得された終了コードがオペレーティング システムにエクスポートされます。プロセスが終了します。
これらのパラメーターは、他の関数のパラメーターと同じです。アーキテクチャの呼び出しシーケンスでパラメーターがスタックを通過する必要がある場合、パラメーターはスタック上にあります。x86-64 のように、いくつかのパラメーターがレジスターに入る場合、これらもレジスターに入ります。
pmg
言及したように、main
が再帰的に呼び出される場合、引数がどこを指すかは呼び出し元次第です。main
「呼び出し元」が C 実装/OS であることを除いて、基本的に答えは の元の呼び出しと同じです。
UNIX-y システムでは、argv
指す文字列、argv
ポインター自体、およびプロセスの初期環境変数は、ほとんどの場合、スタックの一番上に格納されます。
ここで他の多くの回答が指摘しているように、コンパイラの実装が main に引数を渡すために使用する正確なメカニズムは、標準では指定されていません (コンパイラが引数を関数に渡すために使用するメカニズムと同様)。厳密に言えば、値は実装定義であるため、コンパイラはこれらのパラメーターに有用なものを渡す必要さえありません。しかし、これらはどちらも特に役立つ答えではありません。
典型的な C (または C++) プログラムは、いわゆる「ホストされた」実行環境用にコンパイルされます (関数main()
をプログラムの開始点として使用することは、ホストされた環境の要件の 1 つです)。知っておくべき重要なことは、オペレーティング システムによって実行可能ファイルが起動されたときに、関数ではなく、コンパイラのランタイムが最初に制御を取得するようにコンパイラが調整するmain()
ことです。ランタイムの初期化コードは、 への引数用のメモリの割り当てなど、必要な初期化をすべて実行してからmain()
、 に制御を渡しmain()
ます。
への引数のメモリはmain()
、ヒープから取得するか、スタックに割り当てるか (標準 C コードでは利用できない手法を使用する可能性があります)、または静的に割り当てられたメモリを使用することができます。フレキシブル。標準では、によって指される文字列に使用されるメモリargv
が変更可能であり、それらの文字列に加えられた変更がプログラムの存続期間を通じて持続する必要があります。
実行が に到達する前にmain()
、かなりの量のコードが既に実行されており、プログラムが実行される環境を設定していることに注意してください。
通常、それらがどこにあるかは不明です。
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char **foo;
char *bar[] = {"foo", "bar"};
(void)argv; /* avoid unused argv warning */
foo = malloc(sizeof *foo);
foo[0] = malloc(42);
strcpy(foo[0], "forty two");
/* where is foo located? stack? heap? somewhere else? */
if (argc != 42) main(42, foo); else return 0;
/* where is bar located? stack? heap? somewhere else? */
if (argc != 43) main(43, bar); else return 0;
/* except for the fact that bar elements
** point to unmodifiable strings
** this call to main is perfectably reasonable */
return 0;
/* please ignore memory leaks, thank you */
}
引数リストはプロセス環境の一部であり、環境変数に似ています (ただし、環境変数とは異なります)。
実際のパラメーターにアクセスすることはできますが、実際の場所はまったく問題ではないと思います。