みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

コマンドライン引数 *argv[]はなぜポインタ?

C言語初心者です。

コマンドライン引数、
int main(int argc, char *argv[])

というのを最近勉強しましたが、引数2番目がポインタになっている理由について、
どなたか教えて下さい。
そういう仕様なんだから、それに従いましょう、ということでしょうか?

int main(int argc, char argv[]) では、ダメなのでしょうか?

このポインタでの引数渡しについて、
なんらかの納得のいく考え方をご存知の方がいらしたら、教えて下さい。
宜しくお願い致します。

投稿日時 - 2010-11-12 16:02:13

QNo.6314578

困ってます

質問者が選んだベストアンサー

関数への引数の渡し方には大きく分けて以下の2通りあります。

1.値渡し
呼び出す関数へ「値」を渡して、呼び出された関数でのみその値を
使用します。putcharとかがそれで、putcharに値を渡してあげれば、
あとは、putcharの仕事になるので、putcharの返却値を使用するこ
とがありません。

2.参照渡し
これがポインタを渡すやり方で、scanfとかがこの方法になります。
scanf("%d", &a);
などとaの前に&をつけますよね。この&をつけることにより、aのアド
レスを渡してあげるのです。それにより、呼び出し元の関数でaの
値を使用することができます。

で、文字列を扱う場合なんですが、C言語には文字列をそのまま関
数に渡す方法がないのです。そこで仕方なく文字列の先頭のアドレ
スを渡してあげるのです。そうすることにより、呼び出された関数の
中で、文字列を扱うことができます。先頭のアドレスさえわかれば、
あとはそれにつづくアドレスの値が文字列になるのですから・・・。

で、「*a」とか「a[]」とかの書き方で受け取る側は定義します。値とし
ては「*a」も「a[]」も同値です。
ただ、「*a」と定義した場合は、aは(文字列の先頭の)ポインタとして
扱いますが、「a[]」と定義した場合は文字列を配列として扱えます。
a[0]なら先頭1文字、a[1]なら先頭から2番目の文字とゆうように。
例.)
文字列が"hello"だった場合

a[]と定義したら、a[0]は'h'がa[1]は'e'が入っている。

*aと定義したら*aは'h'が、a++とした後に*aの値は'e'になる。

その文字列が複数個あるので、「**a」だったり、「*a[]」とゆう書き方
になるのです。

a[0]がパラメータ1の文字列(の先頭)
a[1]がパラメータ2の  〃
・・・
とゆう感じです。

正直ここらへんはCの難しいところです。でもここを理解できれば、あ
とはほとんど覚えるだけの作業になるので頑張ってください(^_^)

投稿日時 - 2010-11-13 00:53:16

お礼

頂いた回答をすんなり理解できるレベルになかったため、
ポインタについて、さらに詳しく参考書で勉強しておりました。
お礼が遅れて、すみません。

>C言語には文字列をそのまま関数に渡す方法がないのです。
>そこで仕方なく文字列の先頭のアドレスを渡してあげる

大変参考になりました。
頂いた回答は全体的に、大事なことが沢山詰まっており、
とても分かりやすかったです。
ありがとうございました。

投稿日時 - 2010-11-15 00:32:35

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(9)

ANo.9

例えば、下の様に動かして見るとどうなるか考えて見てください。

command arg1 arg2 filename

argv[0] = "command"
argv[1] = "arg1"
argv[2] = "arg2"
argv[3] = "filename"

上記をコピーしようと思うと、

char **copyarg;

copyarg = (char **)malloc( sizeof(char *) * 4 ); /*文字列の配列*/
copyarg[0]=(char *)malloc( sizeof(char) * ( strlen(argv[0]) + 1) );
strcpy( copyarg[0], argv[0] ); /* "command" 文字列 */
copyarg[1]=(char *)malloc( sizeof(char) * ( strlen(argv[1]) + 1) );
strcpy( copyarg[1], argv[1] ); /* "arg1" 文字列 */
copyarg[2]=(char *)malloc( sizeof(char) * ( strlen(argv[2]) + 1) );
strcpy( copyarg[2], argv[2] ); /* "arg2" 文字列 */
copyarg[3]=(char *)malloc( sizeof(char) * ( strlen(argv[3]) + 1) );
strcpy( copyarg[3], argv[3] ); /* "filename" 文字列 */


char argv[4][9] ではありません。(全文字列用に最大の9文字分のメモリが割り当てられている訳ではありません)
argv[0][8]
argv[1][5]
argv[2][5]
argv[3][9]

char *argv[4] もしくは char **argvです。


下記のソースをコンパイルして動かしてみてください。
引数に与える文字列の長さを色々変えて確認してみてください。
アドレスの差を見ると、文字列長+1 になるのではないかな。(最大文字の配列なら全部同じ差になるはず)
メモリが無駄に使われてない事が解るのではないかな。
※ argv[m][n]の様に全部の文字列を最大文字列長で確保している訳ではない。


#include <stdio.h>

int main( int argc, char *argv[] )
{
int n;

for( n = 0; argc >n; n ++ ) {
printf( "No.%d Addr:0x%x String:%s\n", n, argv[n], argv[n] );
}
}



No.0 Addr:0xbfbfecd8 String:./a.out
No.1 Addr:0xbfbfece0 String:command
No.2 Addr:0xbfbfece8 String:arg1
No.3 Addr:0xbfbfeced String:arg2
No.4 Addr:0xbfbfecf2 String:filename


No.0 Addr:0xbfbfecd4 String:./a.out
No.1 Addr:0xbfbfecdc String:a
No.2 Addr:0xbfbfecde String:BC
No.3 Addr:0xbfbfece1 String:def
No.4 Addr:0xbfbfece5 String:GHIJK
No.5 Addr:0xbfbfeceb String:lmnopurstuv
No.6 Addr:0xbfbfecf7 String:WXY

a 2 ( 0xbfbfecde - 0xbfbfecdc )
BC 3 ( 0xbfbfece1 - 0xbfbfecde )
def 4 ( 0xbfbfece5 - 0xbfbfece1 )
※ "def" は 'd' 'e' 'f' '\0' ですから 4 バイトですね。

投稿日時 - 2010-11-14 02:38:58

お礼

>メモリが無駄に使われてない事が解るのではないか

なるほど、勉強になりました。
参考にさせて頂きます。
ありがとうございました。

投稿日時 - 2010-11-15 00:53:14

ANo.8

#2 の「引数の扱いを平易にするため」というのは, たとえばこんなことです:
プログラムを
foo arg1 arg2 longlongarguments
と起動した場合, 今の C の仕様 (char *[] で渡す) だと
argv[0]: "foo"
argv[1]: "arg1"
argv[2]: "arg2"
argv[3]: "longlongarguments"
argv[4]: NULL
となります. 一方あなたの言うように char [] で渡すと
"foo arg1 arg2 longlongarguments"
という「文字列」が渡ります. どちらが「より簡単に引数を扱える」と思いますか?

投稿日時 - 2010-11-13 11:44:16

お礼

文字列を、バラバラな状態で渡すために、
文字列それぞれの先頭アドレスを配列に入れて渡しているわけですね。

文字の配列と文字列の配列の違いに気付かされました。
ありがとうございます。

投稿日時 - 2010-11-15 00:46:58

ANo.7

>引数2番目がポインタになっている

ポインタではなく、「ポインタの配列」です。
Tacosanさんの回答のとおり。

投稿日時 - 2010-11-13 07:11:43

お礼

>ポインタではなく、「ポインタの配列」

そうでしたね。
質問当初は、よく分かっておりませんでした。
今はおかげさまで、だいぶ分かるようになりました。
ありがとうございます。

投稿日時 - 2010-11-15 00:41:09

↓の図がわかりやすいかなぁ。

参考URL:http://ysserve.int-univ.com/Lecture/c2/e_04-03.html

投稿日時 - 2010-11-13 02:00:33

お礼

ポインタの説明には、確かにメモリの図があった方が分かりやすいですね。
私は参考書の図を見て、理解することができました。
参考になるページを教えて下さり、ありがとうございます。

投稿日時 - 2010-11-15 00:38:07

ANo.5

引数 1個ずつが「文字列」 = char * で, それがたくさんあるからその配列となって char *[], ってだけなんだけどなぁ....
ちなみに関数の仮引数などでは char ** と char *[] は同じ (どちらも char ** と解釈される) ですが, 配列とポインタは本来「違うもの」です.

投稿日時 - 2010-11-13 01:08:06

お礼

>引数 1個ずつが「文字列」 = char * で, それがたくさんあるからその配列となって char *[], ってだけなんだけどなぁ....

この説明は、「ポインタの配列」を理解できた後に、理解することができました。

>配列とポインタは本来「違うもの」です.

この点も勉強になりました。
ありがとうございます。

投稿日時 - 2010-11-15 00:35:27

ANo.3

まず、文字列は
char a[10];
とかで表現しますよね?

そうするとパラメータで渡す場合は「*a」または「a[]」となります。

では、複数の文字列は
char a[5][10]
とかで表現しますよね?

そうするとパラメータで渡す場合は「**a」または「a[][]」または「*a[]」と
なります。

引数では
a.exe パラメータ1 パラメータ2
と複数のパラメータを指定したい場合があります。

そうすると複数文字列の場合になるので、「argv[]」ではダメで
「*argv[]」となります。
ちなみに「**arg」と書いても問題ないし、そう書く人もいます。

投稿日時 - 2010-11-12 16:37:13

お礼

>そうするとパラメータで渡す場合は「*a」または「a[]」

すみません、この段階で既につまずいております。
ただ、これが分かれば、後はスムーズに理解できそうだと思っているのですが…。

ポインタの使用意義が私はまだよく分かっていないようです。

>「**a」または「a[][]」または「*a[]」

そもそも、これらは全て、同値なのでしょうか。
もう、この辺りはさっぱりわかりません。すみません。泣

投稿日時 - 2010-11-12 20:35:45

ANo.2

argvの型はchar **と書かれることもあります。

まぁそんなことはさておきますが、引数が複数あった場合に(char *)[]なら単純にargvを配列参照すれば各引数にアクセスできますが、char []だとわざわざ分割して解析して、という手順が必要になります。

ということで「引数の扱いを平易にするため」ということでどうですか?

投稿日時 - 2010-11-12 16:31:27

お礼

>(char *)[]なら単純にargvを配列参照すれば各引数にアクセスできますが、char []だとわざわざ分割して解析して、という手順が必要に

(char *)[]

こういった書き方もあるのでしょうか?

>配列参照すれば各引数にアクセスできます
>わざわざ分割して解析

この辺りが、おそらく本件の回答の核であるように感じましたが、
初心者の私には、上記の意味するところがよく分かりませんでした。
もし可能でしたら、それが意味する内容を、もう少し噛み砕いてご説明頂けますと嬉しいです。
回答、ありがとうございます。

投稿日時 - 2010-11-12 20:27:07

ANo.1

複数の引数を渡しやすいようにするためでしょう。

投稿日時 - 2010-11-12 16:18:46

お礼

すみません、まだまだ初心者であるため、
おっしゃることの意味がよく分かりませんでした。

ただ、複数の引数の渡しやすさに、しやすさ・しにくさがあって、
ポインタ形式だと(なぜかは分かりませんが)、しやすくなるらしい、
ということが分かりました。
ありがとうございます。

投稿日時 - 2010-11-12 20:21:51

あなたにオススメの質問