ポインタ変数とポインタのポインタ

このQ&Aのポイント
  • ポインタ変数とポインタのポインタについての疑問があります。
  • 特に、ポインタ配列とポインタのポインタの違いがわかりません。
  • プログラムの実例を見ながら、理解を深めたいです。
回答を見る
  • ベストアンサー

ポインタ変数とポインタのポインタ

ポインタ変数の宣言 char *a[]; をしたとき僕の中では a[0],a[1]...という、ある文字列A,B,C...の最初のアドレスを指すポインタが、配列になっているものを宣言していると理解していました。 しかしこの次に、ポインタのポインタが出てきました。僕はこれを、 ある変数を指し示すアドレスのアドレスである、と理解しました。 この2つは1つめはいくつかのアドレスを指し示すもの、2つ目は1つのアドレスを指し示すものであるとして、僕の中で異なったものであると理解していましたが、参考書「C標準コースウェア」によると プログラムにおいて、関数でポインタ配列を受け取るときchar *p[]はchar **pとしてもよい と書かれており、またその実例として、 (9-5) #include <stdio.h> void disp (char *p[],int n){ int i; for (i= 1;i<n;i++){ printf("%s\n",p[i]); } } int main(void){ char *girl[] = {"Arica","Candy","Lisa"}; disp (girl,sizeof(girl)/sizeof(girl[0])); return 0; } というプログラムが書かれていました。 ここで一気に訳が分からなくなりました。 char *girl[] = {"Arica","Candy","Lisa"}; と宣言されているため、 girl[0]はAricaという文字列の最初のアドレスを指すポインタ、 *girl[0]はAricaという文字列を直接指し示していると解釈しています。 girlは{"Arica","Candy","Lisa"}という文字列の配列の最初のアドレスを指し示していると考えました。 sizeof(girl)を使った時に不思議なのですが、 girlはどのように配列の終わりを理解しているのでしょうか? (配列の要素数を渡していない点が不思議です。) また、 disp側が受け取ったのは*girl[]であり、いくつかのポインタの配列ですが、渡したものはgirlという要素数がないポインタ1つだけです。 そして最初の疑問が出てくるわけですが、*p[]を**pと書きかえてみると、 文字列のアドレスを示すgirlという名の1つのポインタを渡すと、pという名のポインタのポインタで受け取るというのも、よくわからなくなっています。 おそらくポインタ配列に対する理解がどこかでずれているようですが、自分でどこがわからないのかわからなくなっています。 どうかご教授ください。

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

  • ベストアンサー
noname#208507
noname#208507
回答No.3

おそらく、実際に試してみるのが手っ取り早いでしょう。 > girlはどのように配列の終わりを理解しているのでしょうか? コンパイラが配列サイズを知っています。下の test2.c をコンパイルしてみれば、コンパイラがサイズを知らない配列は sizeof できないことが分かると思います。 > 文字列のアドレスを示すgirlという名の1つのポインタを渡すと、 > pという名のポインタのポインタで受け取るというのも、 > よくわからなくなっています。 質問者さんご自身が、質問で引用されていた「関数でポインタ配列を受け取るとき」という部分が肝心です。 配列とポインタを、必ずしも同じように扱えるわけではありません。同じように扱える例を先に学ばれてしまったのが混乱の元になっていると思います。下の test1.cとtest3.c、および test1.cとtest4.c を分割コンパイル/リンクしたプログラムを実行して、比較してみてください。 そして test5.c をコンパイル/実行してみれば、関数の仮引数には実引数そのものではなく、コピーが渡されていることが分かると思います。ポインタの配列でも、ポインタのポインタであっても。「関数でポインタ配列を受け取るとき」char *p[]はchar **pとしてもよいのはこのためです。 // ----- test1.c ----- // (要 分割コンパイル) char *boy[] = {"Dahl", "Dijkstra", "Hoare"}; // ----- test2.c ----- #include <stdio.h> int main(void){ char *girl[] = {"Arica","Candy","Lisa"}; extern char *boy[]; printf("%zu\n", sizeof(girl)); // サイズは既知 printf("%zu\n", sizeof(boy)); // サイズ不明(コンパイルエラー) return 0; } // ----- test3.c ----- // 配列に正常にアクセスするケース #include <stdio.h> int main(void){ extern char *boy[]; printf("%p\n", boy); // 配列の正しいアドレス printf("%p\n", &boy); // 上と同一のアドレス printf("%s\n", boy[0]); return 0; } // ----- test4.c ----- // 配列をポインタと同様に扱うと問題を起こすケース #include <stdio.h> int main(void){ extern char **boy; printf("%p\n", &boy); // ポインタ自身のアドレスと見なされる printf("%p\n", boy); // 余分な評価で、不正なアドレスに printf("%s\n", boy[0]); // メモリアクセス・エラー return 0; } // ----- test5.c ----- // 配列、ポインタを関数の引数にした場合 #include <stdio.h> char *girl[] = {"Arica","Candy","Lisa"}; void disp(char *array[], char **pointer){ printf("%p\n", array); // 配列の正しいアドレス printf("%p\n", &array); // 仮引数 array のアドレス printf("%p\n", pointer); // 配列の正しいアドレス printf("%p\n", &pointer); // 仮引数 pointer のアドレス printf("%s %s\n", array[0], pointer[0]); } int main(void){ disp(girl, girl); return 0; }

pipopipoid
質問者

お礼

>コンパイラが配列サイズを知っています。 ありがとうございます。その事実は非常に考えさせられます。配列はメモリ上にあるときサイズの情報を持っていない->ない、という図式で考えていたのでコンパイラが関係しているとは思いませんでした。 test4.c extern char **boy; は関数以外が*boy[]を受け取るのに**boyでは無理だという説明のために使ったため、おかしく感じて当たり前ですよね? test.5.cはある程度の理解ができるようになるましたが、やはり理解できない部分があるので図で説明しようと思います。 char *girl[] = {"Arica","Candy","Lisa"};で *girl[0]= "Arica\0" *girl[1]= "Candy\0" *girl[2]= "Lisa\0" を宣言し、それぞれの文字列の最初のポインタがgirl[0],girl[1],girl[2]に入ると考えています。 すなわち、 *girl[0]= "Arica\0" ↑girl[0] *girl[1]= "Candy\0" ↑girl[1] *girl[2]= "Lisa\0" ↑girl[2] と考えています。(書いた後気づきましたが、空白が詰められますね。girl[0]girl[1],girl[2]はそれぞれの文字列の最初のアドレスを指し示しています。) この図で考える限り、girl[0],girl[1],girl[2]は連続しておらず、 **pointerで受け取った場合、girlの最初のアドレスを受け取ってもgirl[1],girl[2]は離れているため推定することができないのではないかと思っています。 これが*array[]で受け取った場合であっても、アドレスをarrayという配列で受け取る、と考えれば理解できないこともないですが、girlは1つのポインタしか指していないので、やはりこれもおかしい、となります。

pipopipoid
質問者

補足

すいません、お礼を書いた後に書き込んでいますが、girl[0],girl[1],girl[2]は連続していますね・・・。 図に引っ張られすぎてこんがらがっていました。 考えていたら、だいぶ納得できたように思います。

その他の回答 (2)

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.2

あ~, 「最初のアドレスを指すポインタ」って表現 (より狭くは「アドレスを指す」のところ) が微妙にあやしいのか.... 「最初の要素を指すポインタ」ならいいんだけど. C において配列名は (たいていの場合に) 「その配列の先頭要素へのアドレス」に変換される. これは「int の配列」だろうと「char * の配列」だろうと同じこと. で元の質問の文章からおかしな点をいくつか取り出すと ・「*girl[0]はAricaという文字列を直接指し示している」: そんなことはない... というか, 「直接指し示している」ってどんな状態? ・「disp側が受け取ったのは*girl[]であり、いくつかのポインタの配列ですが」: C では「配列を受け取る」ことはできない. ポインタなら受け取れる. だからこそ「char *p[]はchar **pとしてもよい」んだけど... いや, 本当は「char **p は char *p[] としてもよい」の方が正しいか. ・「文字列のアドレスを示すgirlという名の1つのポインタを渡すと」: 「文字列のアドレスを示す」とは, どういうことでしょうか? くらいは挙げられるかな. ちなみにですが, #1 のプログラムで「girlはどのように配列の終わりを理解しているのでしょうか」という疑問は持ちませんでしたか? 疑問ではないというなら, 説明してみてください. あと「前半部分が理解できないままです」の「前半部分」ってどの部分でしょうか.

pipopipoid
質問者

お礼

ちなみにですが, #1 のプログラムで「girlはどのように配列の終わりを理解しているのでしょうか」という疑問は持ちませんでしたか? 疑問ではないというなら, 説明してみてください. といいますが、 質問文中に >girlはどのように配列の終わりを理解しているのでしょうか? (配列の要素数を渡していない点が不思議です。) と書いているのですが読み落とされているのでしょうか? すみません、きちんと質問文を読んでくださっているのでしょうか?わからないから質問しているのに○○って知ってる?と同じこと聞かれると、非常にかみ合っていない感覚に陥ります。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

まず「ポインタのポインタ」を「ある変数を指し示すアドレスのアドレス」と理解しちゃったところが間違い. 「××のポインタ」といえば, それは「××という変数のアドレス (を保持する変数)」という意味. つまり「ポインタのポインタ」は「ポインタ変数のアドレス (を保持する変数)」ということ. 後半については, そもそも配列とポインタに関する理解が足りていないんだと思う. 例えば #include <stdio.h> void disp (int p[],int n){ int i; for (i= 1;i<n;i++){ printf("%d\n",p[i]); } } int main(void){ int girl[] = {5, 1002, 79}; disp (girl,sizeof(girl)/sizeof(girl[0])); return 0; } だったらわかる?

pipopipoid
質問者

お礼

ある変数を指し示すアドレスのアドレス はポインタのポインタのポインタと勘違いしていました。 ポインタのポインタはアドレスを記憶する変数であるという点は大丈夫になったと思います。 しかし前半部分が理解できないままです。 例に示されたプログラムは理解できます。

関連するQ&A

  • ポインタのポインタの必要性

    書物によるとポインタのポインタの使用例として「ポインタの配列」はポインタを使ってアクセスすることができます。」[*]とありますが、どうしてポインタのポインタが必要なのかがいまいちピンと来ません。 どういう場合なのかを知りたく思っています。 [*]サンプルスクリプト ===================================================== char *mnthp[3] = {/* ポインタの配列の宣言 */ "January", "February", "March" }; char **p1;/* 「ポインタのポインタ」の宣言 */ int i, j; p1=mnthp;/* 「ポインタのポインタ」にポインタの配列 */ /* の先頭番地を設定 */ /***** 例1 *****/ for (i = 0; i < 3; i++) {/* 「ポインタのポインタ」の値を変えずに */ printf("%s\n", *(p1 + i));/* 相対的に文字列を出力 */ } ==> このようなことをしなくとも printf("%s", mnthp[i]); で値を参照出来ると思われる。 ===================================================== [*] http://www9.plala.or.jp/sgwr-t/c/sec10-4.html 宜しくお願い致します。

  • ポインタ

    文字列"apple", "orange", "strawberry"へのポインタをポインタ配列の各要素に代入した後,その文字列の文字を逆順に表示するようにプログラムを考えているのですが、 while文の中のjはそれぞれについて考える必要がありますか? ポインタを使って文字数を数得られそうですが出来ませんでした。 [実行例] ポインタ配列[0]の文字列の逆はelppaです. ポインタ配列[1]の文字列の逆はegnaroです. ポインタ配列[2]の文字列の逆はyrrebwartsです. #include<stdio.h> #define COUNT 3 int main(void) {   char * words[COUNT] = {"apple", "orange", "strawberry"};   int i, j;   for(i = 0; i < COUNT; i++) {    j =  ?  ;    printf("ポインタ配列[%d]の文字列の逆は", i);    while(   ?   ) {     printf("%c", *(words[i] + j));     j--;    }    printf("です.\n");   }   return 0; }

  • ポインタ配列の動的確保

    ポインタの配列の動的確保について教えてください。 入力した数値をポインタ配列に入れるプログラムです。 下記のように書いてみました。(見づらくてごめんなさい) #include<stdio.h> #include<stdlib.h> #define kensu 3 main() { char abc[kensu+1]={'A','B','C','\0'}; char *ptr[kensu]; int i; printf("3つの整数を入力して下さい。\n"); for(i=0;i<kensu;i++){ ptr[i]=(char*)malloc(sizeof(char)*10); if(ptr[i]==NULL){ printf("メモリの取得に失敗しました"); exit(1); } printf("整数%c:",abc[i]); fgets(ptr[i],10,stdin); if(ptr[i][strlen(ptr[i])-1]=='\n') ptr[i][strlen(ptr[i])-1]='\0'; } for(i=0;i<kensu;i++) free(ptr[i]); } ちゃんと動いているようです。 しかし、ポインタ配列の動的確保をネットで調べてみると、ポインタのポインタ(?)を使って、下記のように2度mallocしています。 #include <stdio.h> #include <stdlib.h> #define N 3 int main(void) { char** arr; int i,j; arr = (char**)malloc(N * sizeof(char*)); /* ポインタ配列を確保 */ /* 配列の要素それぞれにつき、メモリ領域を確保 */ for(i=0;i<N;i++) arr[i] = (char*)malloc(N * sizeof(char));   ・・・ ポインタの配列を宣言して、配列の各要素に動的確保するのと ポインタのポインタを宣言し、ポインタ配列を動的確保して、再度配列の要素に動的確保するのとでは、何か違いがあるのでしょうか? ポインタのポインタを宣言し、ポインタ配列を確保する必要性が良く分かっていないのです。 ネット等で調べて見たのですが、理解力がないのかよく分かりませんでした。 どうか教えてください。

  • char型のポインタ配列に変数の値の代入できる?

    c言語でchar型のポインタ配列に変数の値を代入できるのでしょうか? 例えば int A[10]={1,2,3,4,5,6,7,8,9,10}; char *C[10]; のCに配列Aの中のデータを文字列として入れたいのです。 C[0]="A[0]"としてもA[0]という文字列が代入されてしまうだけなので… よろしくお願いします。

  • void型ポインタについて

    -------------------------------- #include<stdio.h> void uni_disp(void *p,int typ); int main() { int idt=123456; double ddt=56.789; char ss[]="abcdef"; uni_disp(&idt,'I'); uni_disp(&ddt,'D'); uni_disp(ss,'S'); uni_disp("STRING",'S'); return 0; } void uni_disp(void *p,int typ) { if(typ=='I'){ printf("%d\n",*(int *)p); } else if(typ=='D'){ printf("%f\n",*(double *)p); } else if(typ=='S'){ printf("%s\n",(char *)p); } } ----------------------------------- 以上のプログラム等で、void型ポインタをint型ポインタ、double型ポインタとみなす場合の、「*(int *)p」や「*(double *)p」の表記がどういう仕組みになっているか分かりません。 「*(int)p」などはエラーが出るのですが、やはり表記の意味を理解していないため何故か分かりません。 「*(int *)p」などの表記を分解して教えていただけると嬉しいです。

  • ポインタのポインタの初期化法

     文字列をポインタを使って扱うとき、例えば、初期化は次のように行えますよね。 #include <stdlib.h> char *s; s = (char *)malloc(1000); (これに続いてscanf("%s", s);など)  これと同様にして、二次元の配列を、ポインタのポインタを使って表したいとき、 char **s; と宣言したものを、malloc()関数を使って初期化することはできるのでしょうか。よろしくお願いします。

  • ポインタのポインタの使い方

    文字列の途中からの部分を表示させる場合もあって、途中からの 位置というのをcahr**型の配列に記憶させることにしました。 ポインタとnewがよく分からないから、それに近いサンプルを 作って実験しているところです。 #include <iostream.h> main() {  char selected[][6] = {"full", "nomal", "short"};  char *buf;  char **p;  buf = new char[500];  strcpy(buf, ",東京都,千代田区,九段南");  p = new char*[3];  for(unsigned int i=0, j=0; i<strlen(buf); i++)  {   if(buf[i] == ',')p[j++] = &buf[i+1];  }  for(int i=0; i<3; i++)  {   cout << selected[i] << "-" << p[i] << endl;  }  delete [] buf;  delete [] p; } 結果は full-東京都,千代田区,九段南 nomal-千代田区,九段南 short-九段南 で、問題なさそうに見えるけど心配だから質問しました。 このソースに問題はないですか? 得に、 char **p; と p = new char*[3]; と p[j++] = &buf[i+1]; と delete [] buf; が心配です。

  • ポインタ配列

    "one","two","three","four","five","six","seven","eight","nine","ten" のポインタ配列の文字列を、ASCIIコード順に並べ変えようと思ったのですが、 もうどこが間違っているかさえわからないぐらいになってしまいました。 まだまだはじめたばかりなもので、わからないことだらけなんで、 できるだけわかりやすい説明おねがいします。 関数の引数に問題があるのじゃないかと思ったのですが、 何かいいアドバイスありましたら、お願いします。 #include <stdio.h> /* 関数のプロトタイプ宣言 */ int strmp(char *,char *); void cpy(char *,char *); int main (void) { /* ポインタ配列の定義 */ char *x[10]={"oneee","twooo","three","fourr","fivee","sixxx","seven","eight","ninee","tennn"}; /* ポインタのポインタの定義 */ char **pp=x; char k[100]; char *p=k; int i,t,a,b,c,d; a=0; /* ポインタ配列を自作関数を使って、ASCIIコードの大きいほうからに並び替える */ for(i=0;i<9;i++) { for(t=1;t<10;t++) { a=strmp(*(pp+i),*(pp+t)); if(a<0) { cpy(p,*(pp+i) ); cpy(*(pp+i),*(pp+t) ); cpy(*(pp+t),p); } } } for(i=0;i<10;i++) { printf("%s ,",x[i]); } printf("\n"); return 0; } /* 文字の比較をする関数 */ int strmp(char *x,char *y) { int i; for(i=0;*(x+i)==*(y+i);i++) { if( *(x+i)=='\0') { return 0; } } return *(x+i)-*(y+i); } /* 文字をコピーする関数 */ void cpy(char *a,char *b) { int i; for(i=0;*(b+i)!='\0';i++) { *(a+i)=*(b+i); } *(a+i)='\0'; }

  • C言語 ポインタ変数について

    ポインタ変数の値とアドレスの表示の仕方がよくわからないため、教えていただけますでしょうか。 --------------------------------------------------------------------- #include <stdio.h> int main() {    char* animal[] = {"dog", "cat", "hamster", "mouse"};    int i;    for(i=0; i<4; i++) {      printf("%p animal[%d] : %s, アドレス : %p\n",      animal+i, i, animal[i], *(animal+i));    }    return 0; } --------------------------------------------------------------------- このプログラムを実行すると 0022FF40 animal[0] : dog, アドレス : 00403000 0022FF44 animal[1] : cat, アドレス : 00403004 0022FF48 animal[2] : hamster, アドレス : 00403008 0022FF4C animal[3] : mouse, アドレス : 00403010 となるのですが、なぜanimal[i]のところに配列の要素(dog, cat,・・・)が表示されるのかがわかりません。 char* animal[] = {"dog", "cat", "hamster", "mouse"};の行では、 animal[0] = "dog" なら animal[0]には"dog"の先頭アドレス(00403000)が代入されているのではないのでしょうか。

  • ポインタの配列について

    はじめまして。 基本的な質問で恐縮ですが、質問があります。 例えば、char *name[] = {"Alice","Ben","Chris","Diana","Elmo"} のようなポインタの配列を宣言・初期化したとき、配列nameの各要素の 中身には、初期化子として与えた、文字列の先頭アドレスが 格納されているという解釈でよろしいのでしょうか?つまり、 *(name+1)やname[1]は、「文字列Benの先頭アドレスを指す」という意 味でよろしいのでしょうか?また、このとき、(name+1)というように *をつけないときは、「文字列Benの先頭アドレスを格納したメモリの 先頭アドレス」というような解釈でよろしいのでしょうか? どうかご教授お願いします。