C/C++では、メイン関数はタイプchar*
のパラメーターを受け取ります。
int main(int argc, char* argv[]){
return 0;
}
argv
はchar*
の配列で、文字列を指します。これらの文字列はどこにありますか?それらはヒープ、スタック、または他のどこにありますか?
これは実際には、コンパイラ依存とオペレーティングシステム依存の組み合わせです。 main()
は他のC関数と同様に関数であるため、2つのパラメーターargc
およびargv
の場所は、プラットフォーム上のコンパイラーの標準に従います。例えばx86をターゲットとするほとんどのCコンパイラーでは、戻りアドレスと保存されたベースポインターのすぐ上のスタックに配置されます(スタックは下向きに増加することを覚えておいてください)。 x86_64では、パラメーターはレジスターで渡されるため、argc
は_%edi
_にあり、argv
は_%rsi
_にあります。コンパイラーによって生成されたメイン関数のコードは、それらをスタックにコピーします。これは、後で参照する場所です。これは、レジスタをmain
からの関数呼び出しに使用できるようにするためです。
Argvが指す_char*
_ sのブロックと実際の文字シーケンスはどこにあってもかまいません。それらは、オペレーティングシステムによって定義された場所で開始され、リンカがスタックまたは他の場所に生成するプリアンブルコードによってコピーされる場合があります。 exec()
のコードと、リンカによって生成されたアセンブラのプリアンブルを調べて調べる必要があります。
それらはコンパイラの魔法であり、実装に依存しています。
C標準( n1256 )の説明は次のとおりです。
5.1.2.2.1プログラムの起動
...
2それらが宣言されている場合、 メイン 関数は次の制約に従います。
- の価値 argc 負ではないものとします。
- argv [argc] ヌルポインタでなければならない。
- の値 argc ゼロより大きい場合、配列のメンバー argv [0] 使って argv [argc-1] インクルーシブには、プログラムの起動前にホスト環境によって実装定義の値が与えられる文字列へのポインタが含まれます。その目的は、ホスト環境の他の場所からプログラムを起動する前に決定されたプログラム情報を提供することです。ホスト環境が文字列を大文字と小文字の両方で提供できない場合、実装は文字列が小文字で受信されるようにします。
- の値 argc がゼロより大きい場合、文字列は argv [0] を表す プログラム名; argv [0] [0] ホスト環境からプログラム名を取得できない場合は、ヌル文字になります。の値 argc が1より大きい場合、が指す文字列 argv [1] 使って argv [argc-1] を表す プログラムパラメータ。
- パラメータ argc そして argv とが指す文字列 argv 配列はプログラムによって変更可能であり、プログラムの起動から終了までの間、最後に格納された値を保持します。
最後の箇条書きは、文字列値が格納される最も興味深いwrtです。ヒープやスタックは指定しませんが、文字列が書き込み可能で静的なエクステントを持っている必要があります。これにより、文字列の内容の場所にsome制限が設定されます見つけることができます。他の人が言ったように、正確な詳細は実装に依存します。
この質問への答えはコンパイラに依存します。これは、C標準では扱われていないため、だれでも自由に実装できます。オペレーティングシステムにも、プロセスを開始して終了するための一般的に受け入れられている標準的な方法がないため、これは正常です。
単純な理由のないシナリオを想像してみましょう。
プロセスは、何らかのメカニズムによって、コマンドラインで記述された引数を受け取ります。 argcは、bootstrap関数がコンパイラにプログラムのプロセス(ランタイムの一部)のエントリポイントとして配置する)によってスタックにプッシュされる単なるintです。実際の値は、オペレーティングシステムから、たとえば、ヒープのメモリブロックに書き込むことができます。次に、argvベクトルが構築され、最初の位置へのアドレスもスタックにプッシュされます。
次に、プログラマーが提供する必要のある関数main()が呼び出され、その戻り値は後で(ほぼ中間で)使用するために保存されます。ヒープ内の構造が解放され、メイン用に取得された終了コードがオペレーティングシステムにエクスポートされます。プロセスが終了します。
これらのパラメーターは、他の関数のパラメーターと同じです。アーキテクチャの呼び出しシーケンスがスタックを通過するためにパラメータを必要とする場合、それらはスタック上にあります。オンのように、x86-64の一部のパラメーターがレジスターに入る場合、これらのパラメーターもレジスターに入る。
ここで他の多くの回答が指摘しているように、コンパイラ実装が引数をmainに渡すために使用する正確なメカニズムは、規格によって指定されていません(コンパイラが関数に引数を渡すために使用するメカニズムと同様)。厳密に言えば、値は実装によって定義されるため、コンパイラはこれらのパラメータで有用なものを渡す必要さえありません。しかし、これらはどちらも特に役立つ回答ではありません。
典型的なC(またはC++)プログラムは、「ホストされた」実行環境として知られているもののためにコンパイルされます(プログラムの開始点はホスト環境の要件の1つであるため、関数main()
を使用します)。知っておくべき重要なことは、オペレーティングシステムによって実行可能ファイルが起動されると、コンパイラのランタイムが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 */
}
pmg
が言及しているように、main
が再帰的に呼び出される場合、引数が指すのは呼び出し元次第です。基本的に、「呼び出し元」がCの実装/ OSであることを除いて、元のmain
の呼び出しの答えは同じです。
UNIX-yシステムでは、argv
が指す文字列、argv
ポインター自体、およびプロセスの初期環境変数は、ほとんど常にスタックの最上部に格納されます。
引数リストはプロセス環境の一部であり、環境変数と似ていますが、環境変数とは異なります。