char ** argvまたはchar * argv []を使用する必要がありますか?


125

私はCを学習しているだけで、メインメソッドでこれらのどれを使用する必要があるのか​​と思っていました。違いはありますか?どちらがより一般的ですか?


2
私は「char ** argv」を好むので、より頻繁に表示されますが、どちらも正しく、「char * argv []」と書かれただけで宣言を変更することはありません。
ジョナサンレフラー

+1は、配列とポインタの深い問題がよく理解するために重要であるためです。
RBerteig 2009

2
本当に、本当にいい質問です。ありがとうございました。
フランクV

5
comp.lang.c FAQのセクション6を読んでください。これは、私が見たCの配列とポインタの関係を最もよく説明しています。関連する2つの点:1. char **argvchar *argv[]、パラメーター宣言とまったく同じです(パラメーター宣言としてのみ)。2.配列はポインタではありません。
キース・トンプソン

回答:


160

Cを学習しているだけなので、一般的なことではなく、配列とポインタの違いを最初に理解することをお勧めします。

パラメータと配列の領域では、先に進む前に明確にしておくべきいくつかの混乱するルールがあります。まず、パラメーターリストで宣言したものは特別に扱われます。Cの関数パラメーターとしては意味がないような状況があります。これらは

  • パラメータとして機能
  • パラメータとしての配列

パラメータとしての配列

2番目は多分すぐに明確ではありません。しかし、配列の次元のサイズがCの型の一部である(そして、次元のサイズが指定されていない配列の型が不完全である)と考えると、明らかになります。したがって、配列を値渡し(コピーを受け取る)する関数を作成する場合、1つのサイズでしか実行できません。さらに、配列は大きくなる可能性があり、Cはできるだけ高速にしようとします。

Cでは、これらの理由により、配列値は存在しません。配列の値を取得する場合、代わりに取得するのはその配列の最初の要素へのポインターです。そして、ここに実際にすでに解決策があります。無効な配列パラメーターを事前に描画する代わりに、Cコンパイラーはそれぞれのパラメーターの型をポインターに変換します。これを覚えておいてください、それは非常に重要です。パラメータは配列ではなく、それぞれの要素タイプへのポインタになります。

ここで、配列を渡そうとすると、代わりに渡されるのは配列の最初の要素へのポインターです。

エクスカーション:パラメータとしての機能

完了のために、そしてこれは問題をよりよく理解するのに役立つと思いますので、関数をパラメーターとして使用しようとするときの状況を見てみましょう。確かに、最初は意味がありません。パラメータはどのように関数にすることができますか?ええと、もちろんその場所に変数が必要です!そのため、コンパイラーがこれを行うときに行うことは、やはり、関数を関数ポインター変換することです。関数を渡そうとすると、代わりにそれぞれの関数へのポインタが渡されます。したがって、以下は同じです(配列の例と同様)。

void f(void g(void));
void f(void (*g)(void));

括弧*gが必要であることに注意してください。それ以外の場合は、関数void*returnへのポインタの代わりに、関数returnを指定しますvoid

アレイに戻る

さて、冒頭で、配列の型が不完全になる可能性があることを最初に述べました。これは、サイズをまだ指定していない場合に起こります。配列パラメーターが存在しないことはすでにわかりましたが、代わりに配列パラメーターはポインターであるため、配列のサイズは重要ではありません。つまり、コンパイラーは以下のすべてを翻訳しますが、すべて同じものです。

int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);

もちろん、どんなサイズでも入れられるのはあまり意味がなく、捨てられるだけです。そのため、C99ではこれらの数値に新しい意味が生まれ、角かっこの間に他のものが表示されるようになりました。

// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory. 
int main(int c, char *argv[static 5]);

// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);

// says the same as the previous one
int main(int c, char ** const argv);

最後の2行は、関数内で「argv」を変更できないことを示しています。これはconstポインターになっています。ただし、これらのC99機能をサポートするCコンパイラはほとんどありません。しかし、これらの機能により、「配列」は実際には1つではないことが明らかになります。ポインタです。

警告の言葉

関数のパラメータとして配列を取得した場合にのみ、上記で述べたすべてが当てはまることに注意してください。ローカル配列を操作する場合、配列はポインターにはなりません。これは、あろう挙動先に説明したように、その値が読み込まれたときアレイはポインタに変換されるので、ポインタとして。ただし、ポインタと混同しないでください。

典型的な例の1つは次のとおりです。

char c[10]; 
char **c = &c; // does not work.

typedef char array[10];
array *pc = &c; // *does* work.

// same without typedef. Parens needed, because [...] has 
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;

2
これが存在するという考えはありませんでした:* argv [static 1]
superlukas

12

どちらでも使用できます。それらは完全に同等です。litbのコメントと彼の回答を参照してください。

それは本当にそれをどのように使いたいかに依存します(そしてあなたはどちらの場合でも使うことができます):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

どちらがより一般的であるか-それは問題ではありません。コードを読んでいる経験豊富なCプログラマーは、どちらも(適切な条件下で)交換可能と見なします。経験豊富な英語を話す人が「彼らは」と「彼らは」を同じくらい簡単に読むのと同じように。

さらに重要なことは、それらを読むことを学び、それらがどれほど類似しているかを認識することです。あなたはあなたが書くよりも多くのコードを読むでしょう、そしてあなたは両方に等しく慣れる必要があるでしょう。


7
char * argv []は、関数のパラメーター型として使用する場合、char ** argvと100%同等です。「const」は含まれず、暗黙的にも含まれません。どちらも文字へのポインターへのポインターです。宣言する内容が異なります。しかし、たとえあなたがそれが配列であると言ったとしても、コンパイラーはパラメーターの型をポインターへのポインターに調整します。したがって、以下はすべて同じです。void f(char * p [100]); void f(char * p []); void f(char ** p);
Johannes Schaub-litb 2009

4
C89(ほとんどの人が使用する)では、あなたがあなたのことを利用することもできません。 では、配列として宣言し(そのため、意味的には、ポインタと配列のどちらを宣言したかは関係ありません。両方がaとして扱われます。ポインタ)。C99以降では、配列として宣言することでメリットを得られます。「pは常に非ヌルであり、少なくとも100バイトの領域を指している」とは次のように言う:void f(char p [static 100]); ただし、型に関しては、pは依然としてポインターです。
Johannes Schaub-litb 2009

5
(特に、&pはchar **を提供しますが、p(*)が配列である場合は char()[100]を提供しません)。答えに誰も触れていないことに驚いています。理解することはとても重要だと思います。
Johannes Schaub-litb

個人的にchar**は、実際の配列として扱うべきではないことを思い出させるので、sizeof[arr] / sizeof[*arr]ます。
raymai97 2017

9

違いはありませんが、char *argv[]可変長文字列(通常はchar *)の固定サイズの配列であることを示しているので使用します。



4

実際には違いはありませんが、後者の方が読みやすくなっています。あなたが与えられるのは、2番目のバージョンが言うように、charポインタの配列です。ただし、最初のバージョンと同様に、暗黙的にdouble charポインターに変換できます。


2

あなたはそれをとして宣言する必要がありますchar *argv[]。なぜなら、それを宣言する多くの同等の方法があるため、それはその直感的な意味に最も近い文字列の配列です。


1

char **→文字ポインターへのポインターとchar * argv []は、文字ポインターの配列を意味します。配列の代わりにポインターを使用できるため、両方を使用できます。


0

どちらか一方のアプローチを使用するメリットは特にありません。コードの残りの部分と最も一致する規則を使用してください。


-2

可変または動的な数の文字列が必要な場合は、char **の方が扱いやすいかもしれません。文字列の数が固定されている場合は、char * var []が推奨されます。


-2

私はこれが古いことを知っていますが、Cプログラミング言語を習得していて、それで主要なことを何もしていない場合は、コマンドラインオプションを使用しないでください。

コマンドライン引数を使用していない場合は、どちらも使用しないでください。メイン関数をint main() 次のように宣言するだけです

  • プログラムのユーザーがファイルをプログラムにドラッグできるようにして、プログラムの結果を変更できるようにしたり、
  • ハンドルのコマンドラインオプション(にしたい-help/?または後に行く他の事program nameの端末またはコマンドプロンプトで)

あなたにとってより意味のある方を使用してください。それ以外の場合は、int main() 結局のところ、コマンドラインオプションを追加したい場合は、後で簡単に編集できます。


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