C / C ++メインargvが「char * argv」ではなく「char * argv []」として宣言されるのはなぜですか?


21

argv「配列の最初のインデックスへのポインタ」(char* argv)ではなく、「配列の最初のインデックスへのポインタへのポインタ」として宣言されているのはなぜですか?

ここで「ポインターからポインター」の概念が必要なのはなぜですか?


4
「配列の最初のインデックスへのポインタへのポインタ」- char* argv[]またはの正しい説明ではありませんchar**。それは、文字へのポインターへのポインターです。具体的には、外側のポインターは配列内の最初のポインターを指し、内側のポインターはヌル終端文字列の最初の文字を指します。ここに関係するインデックスはありません。
セバスチャンレッド

12
2番目の引数がchar * argvである場合、どのように取得しますか?
gnasher729

15
スペースを適切な場所に置くと、あなたの人生は楽になります。char* argv[]スペースを間違った場所に置きます。と言うとchar *argv[]、これは「式*argv[n]は型の変数である」ことを意味することは明らかですchar。ポインターとは何か、ポインターへのポインターは何かなどを理解しようとすることに巻き込まれないでください。宣言は、このことに対して実行できる操作を示しています。
エリックリッパート

1
char * argv[]同様のC ++コンストラクトと精神的に比較すると、std::string argv[]解析が容易になる場合があります。...実際にそのように書き始めないでください!
ジャスティンタイム2モニカを

2
質問はまた++ Cを含み、そしてあなたが例えばそこに持っていることに注意してください@EricLippert char &func(int);ことはありませんどの&func(5)タイプを持っていますchar
ルスラン

回答:


59

Argvは基本的に次のようなものです。

ここに画像の説明を入力してください

左側は引数そのものです。実際に引数としてmainに渡されるものです。ポインタの配列のアドレスが含まれています。これらはそれぞれ、コマンドラインで渡された対応する引数のテキストを含むメモリ内のある場所を指します。次に、その配列の最後に、nullポインターが保証されます。

個々の引数の実際のストレージは少なくとも潜在的に互いに別々に割り当てられるため、メモリ内のアドレスはかなりランダムに配置される可能性があることに注意してください(ただし、書き込みの方法によっては、メモリ-単に知らないので気にする必要はありません)。


52
レイアウトエンジンがその図を描いたものは何でも、最小交差アルゴリズムにバグがあります!
エリックリッパー

43
@EricLippertは、指示先が隣接していない、または順番に並んでいない可能性があることを強調する意図がある場合があります。
ジェームズドリン

3
私はそれが意図的だと言うだろう
マイケル

24
それは確かに意図的なものであり、おそらくエリックはおそらくそれを理解していたと思いますが、とにかく(正しく、IMO)コメントは面白いと思っていました。
ジェリーコフィン

2
@JerryCoffin、実際の引数がメモリ内で連続していても、任意の長さにすることができるためargv[i]、前の引数をすべてスキャンせずにアクセスできるように、それぞれに個別のポインタが必要になることも指摘できます。
イルカチュウ

22

それがオペレーティングシステムが提供するものだからです:-)

あなたの質問は、鶏/卵の反転の問題です。問題は、C ++で必要なものを選択することではなく、OSが提供するものをC ++でどのように言うかです。

Unixは、「文字列」の配列を渡します。各文字列はコマンド引数です。C / C ++では、文字列は「char *」であるため、文字列の配列は好みに応じてchar * argv []またはchar ** argvです。


13
いいえ、まさに「C ++で必要なものを選択することの問題」です。たとえば、Windowsはコマンドラインを単一の文字列として提供しますが、C / C ++プログラムは引き続きargv配列を受け取ります。ランタイムはコマンドラインのトークン化とargv起動時の配列の構築を処理します。
Joker_vD

14
私はそれがねじれた形で考える@Joker_vD いる OSはあなたを与えるかについて。具体的には、CとUnixは非常に密接にリンクされており、Unixはこのようにしたため、C ++はこのようにしたので、Cはこのようにしたと思います。
ダニエルワグナー

1
@DanielWagner:はい、これはCのUnixの遺産によるものです。Unix / Linuxでは、_start呼び出しを行う最小限の機能は、メモリ内の既存の配列へのポインタをmain渡すだけです。すでに正しい形式になっています。カーネルは、それをargv引数から、新しい実行可能ファイルを開始するために作成されたシステムコールにコピーします。(Linuxでは、argv [](配列自体)とargcはプロセスエントリのスタックにあります。ほとんどのUnixは同じであると思います。それが良い場所だからです。)mainargvexecve(const char *filename, char *const argv[], char *const envp[])
Peter Cordes

8
しかし、ここでのジョーカーのポイントは、C / C ++標準では、引数が由来する実装に任されているということです。OSから直接である必要はありません。フラット文字列を渡すOSでは、フラット文字列全体を設定して渡すのではなく、優れた C ++実装にトークン化を含める必要がありargc=2ます。(標準の文字に続いて、あることには十分ではありません便利な、それは意図的に実装の選択の余地を残します。)実際の実装はフラット文字列を取得する方法を提供しないように、一部のWindowsプログラムは、特別な御馳走引用符になるでしょうが、も。
ピーターコルド

1
Basileの答えは、ほとんどこれと@Jokerの修正と私のコメントであり、詳細があります。
ピーターコーデス

15

まず、パラメーター宣言char **argvとしてchar *argv[]、; と同じです。これらは両方とも、文字列へのポインタ(配列または1つ以上の可能なセット)へのポインタを意味します。

次に、「charへのポインター」のみ(たとえば単に)char *がある場合、n番目のアイテムにアクセスするには、最初のn-1アイテムをスキャンしてn番目のアイテムの開始点を見つける必要があります。(また、これにより、各文字列が連続して格納されるという要件が課せられます。)

ポインターの配列を使用すると、n番目の項目に直接インデックスを付けることができます(厳密には必要ではありませんが、文字列が連続していると仮定した場合)。

説明する:

./program hello world

argc = 3
argv[0] --> "./program\0"
argv[1] --> "hello\0"
argv[2] --> "world\0"

OSで提供される文字の配列は次のとおりです。

            "./program\0hello\0world\0"
argv[0]      ^
argv[1]                 ^
argv[2]                        ^

argvが単なる「charへのポインタ」であれば、

       "./program\0hello\0world\0"
argv    ^

ただし(おそらくOSの設計によるものですが)、3つの文字列 "./program"、"hello"、および "world"が連続しているという本当の保証はありません。さらに、この種の「複数の連続した文字列への単一のポインター」は、特に文字列へのポインターの配列と比較すると、より珍しいデータ型の構造です(Cの場合)。


の代わりに、通常の配列のように(hello)argv --> "hello\0world\0"がある場合はどうでしょうargv --> index 0 of the array。なぜこれができないのですか?その後、配列のargc時間を読み続けます。次に、argvへのポインタではなく、argv自体を渡します。
ユーザー

@auser、それはargv-> "./program\0hello\0\world\0"です:最初の文字(つまり "。")へのポインター最初の\ 0を超えてそのポインターを取ると、 「hello \ 0」へのポインタがあり、その後に「world \ 0」へのポインタがあります。argcの回の後に(\ 0"を押す)、あなたが行われている確かに、それが仕事に行うことができ、そして珍しい構造、私が言ったように。。
エリックEidt

あなたはあなたの例でargv[4]NULL
バシル

3
(少なくとも最初は)という保証がありargv[argc] == NULLます。この場合、それはargv[3]ではありませんargv[4]
ミラル

1
@Hill、はい、ヌル文字のターミネーターについて明示的にしようとしていたので、ありがとうございます(そして、そのターミネーターを見逃しました)。
エリック・エイト

13

C / C ++メインargvが「char * argv []」として宣言されている理由

考えられる答えは、C11標準 n1570§5.1.2.2.1プログラムの起動時およびC ++ 11標準 n3337§3.6.1main関数の場合ホスト環境に対してそれ必要とするためです(ただし、C標準では、また、§5.1.2.1環境自立)も参照してくださいこれを

次の質問は、なぜCおよびC ++標準mainがそのようなint main(int argc, char**argv)署名を選択したのかということです。説明は、主に歴史的である:Cを用いて発明されたのUnix有し、シェルないグロブ行う前forkと、(プロセスを作成するためのシステムコールである)execve(プログラムを実行するためのシステムコールである)を、そのexecve送信アレイ文字列プログラムの引数でありmain、実行されたプログラムの引数に関連しています。Unixの哲学ABIについて詳しく読んでください。

また、C ++はCの規則に準拠し、Cとの互換性を保とうとしました。mainCの伝統と互換性がないと定義することはできませんでした。

オペレーティングシステムをゼロから設計し(まだコマンドラインインターフェイスを使用)、プログラミングシステムをゼロから設計した場合、さまざまなプログラム開始規則を自由に考案できます。また、他のプログラミング言語(Common Lisp、Ocaml、Goなど)では、プログラムの開始規則が異なります。

実際にmainは、いくつかのcrt0コードによって呼び出されます。Windowsでは、crt0に相当する各プログラムによってグロビングが行われ、一部のWindowsプログラムは非標準のWinMainエントリポイントから開始できることに注意してください。Unixでは、グロビングはシェルによって行われます(またcrt0、ABI、および指定された初期呼び出しスタックレイアウトをC実装の呼び出し規約に適合させます)。


12

「ポインターへのポインター」と考えるのではなく、「文字列の配列」と考えると、配列と文字列を[]表すのに役立ちchar*ます。プログラムを実行すると、1つ以上のコマンドライン引数を渡すことができ、これらは次の引数に反映されますmain。:argcは引数の数であり、argv個々の引数にアクセスできます。


2
+1 This!多くの言語(bash、PHP、C、C ++)では、argvは文字列の配列です。これについては、char **またはを見るときに考える必要がありますchar *[]。これは同じです。
rexkogitans

1

多くの場合、答えは「標準だから」です。C99標準を引用するには:

— argcの値が0より大きい場合、配列メンバーargv [0]〜argv [argc-1] には、プログラム起動前にホスト環境によって実装定義の値が与えられる文字列へのポインタが含まれます

もちろん、それが標準化された前に、それは、コマンドラインパラメータを格納する目的で、初期のUnixの実装ではK&R Cによってすでに使用されていた(あなたがUnixの中で気にする必要があり、何かのようなシェル/bin/bash/bin/sh組み込みシステムではありません)。K&Rの「Cプログラミング言語」の初版(110ページ)を引用するには:

最初の(通常argcと呼ばれる)は、プログラムが呼び出されたコマンドライン引数の数です。2番目(argv)は、引数を含む文字列の配列へのポインタで、文字列ごとに1つです。

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