🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

6回答

786閲覧

C言語 ポインタのポインタ、ポインタの配列について

tsuyoku-naritai

総合スコア7

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2021/02/07 10:00

編集2021/02/10 10:12

c言語でポインタのポインタを学習していてわからないサンプルプログラムがあり、それについて質問します。
以下、サンプルプログラムです。

C

1#include <stdio.h> 2 3int main(void){ 4 char *s[3] = { "abc" , "pqr" , "xyz" }; 5 char **pps=NULL; 6 int i; 7 for(i = 0;i < 3;i++){ 8 printf("%s\n",s[i]); 9 } 10 pps = s; 11 for(i = 0;i < 3;i++){ 12 printf("%s\n",*pps); 13 pps++; 14 } 15 return 0; 16}

上記のプログラムで4点質問です。
1点目は、配列sは定義していないのに使えているのか。
2点目は、定義していない配列sのアドレスをppsのアドレスを取得できているのか。
3点目は、**ppsと定義したのに、使用するときに*ppsとなっているのか。
4点目は、*s[3]ではなく、s[3]と宣言すると、s[0]=a,s[1]=b,s[2]=cの様に代入されてしまうのはなぜなのか。
4点目は追加させていただきました。1〜3点目に回答をいただき考えていたのですが、4点目がわからなくなりました。
回答お願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hidezzz

2021/02/10 12:01

> 4点目は、*s[3]ではなく、s[3]と宣言すると、s[0]=a,s[1]=b,s[2]=cの様に代入されてしまうのはなぜなのか。 ただ単に > char s[3] = { "abc" , "pqr" , "xyz" }; のように修正するとエラーとなると思いますが、どのように変更しましたか?(こちらは手元のgcc7.5.0で確認しました)
rubato6809

2021/02/10 15:13 編集

疑問点は原則ひとつに絞るもんじゃないのかね。ポインタ一個なら理解できたんだろうか。
guest

回答6

0

ベストアンサー

1点目、2点目
char *s[3] = { "abc" , "pqr" , "xyz" };
は、char*型で要素数3の配列を宣言/定義し、"abc"のアドレス、"pqr"のアドレス、"xyz"のアドレスを与えて初期化しています。つまり、配列sは定義されています。配列sは定義されて実体を持っているのでアドレスを得ることができます。

3点目
**ppsと定義したのではありません。char**型の変数ppsを宣言、定義しています。
*ppsは、変数ppsに参照演算子*を作用させているものです。なんの問題もないです。


「なぜなのか」それはそういうことに決まっているから、です。
char* s[3];はchar*型の変数3つの配列です。
char s[4];はchar型の変数4つの配列です("abc"は終端文字'\0'が付加されるのでchar4つのスペースを占めます)。
概念図

投稿2021/02/07 10:06

編集2021/02/11 01:25
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Y.H.

2021/02/07 10:30

**~** がMarkdownのboldで解釈されてしまって意図されてない回答になってるので `**pps`と定義したのではありません。`char**`型の・・・・ とされたほうがいいです。
tsuyoku-naritai

2021/02/07 11:00

回答ありがとございます。 3点目は理解できたのですが、1点目、2点目がいまいち理解できません。配列sはポインタ型なのにアドレスを持っているのでしょうか?それとも、ポインタ型の変数でも要素や値が代入されればアドレスを取得できるのでしょうか?
thkana

2021/02/07 11:03

Y.H.さん 御指摘ありがとうございます。修正しました。
thkana

2021/02/07 11:06

> ポインタ型の変数でも要素や値が代入されればアドレスを取得できるのでしょうか? なにか誤解というか拡大解釈というか、ポインタを特殊なものと思っているのでしょうか。 ポインタは、変数です。変数って何? メモリ上に確保された記憶エリアです。他のintだとかdoubleだとかと同じ。存在するだけでアドレスを持ちます。ただそこに格納されているデータの意味(型)がアドレスだ、というだけです。
tsuyoku-naritai

2021/02/07 11:20

すいません、まだポインタを学習したばかりでポインタ自体もよくわかっていないかもしれません。ポインタとは、それ自体もアドレスを持っていて、通常の変数との違いは、アドレスを代入でき、代入した変数になりすませるかどうかということでしょうか?
thkana

2021/02/07 11:41

「通常の変数」と「ポインタ」を区別することからしてなにか勘違いしていると思います。 ポインタは、変数であって、アドレスを格納するものです。 int型は、変数であって、整数を格納するものです。 double型は、変数であって、倍精度浮動小数点を格納するものです。 「変数」としてなにも違いはありません。 「なりすます」という表現もちょっと(かなり)適切でないと思います。*演算子によって、指定されたアドレスにデータがあるものとしてアクセスする、ということです。
rubato6809

2021/02/10 15:16

> 通常の変数との違いは、アドレスを代入でき、代入した変数になりすませるかどうかということでしょうか? たったひとつ、まずこれだけを質問するところから始める位がちょうどよかったんじゃないかね
rubato6809

2021/02/11 06:44

図を描いたのは高評価
thkana

2021/02/11 11:46

言葉だけじゃ説明仕切れそうになかったので。>図
tsuyoku-naritai

2021/02/13 11:12

ありがとうございます。わかった気がします。
guest

0

char s[3] = { "abc" , "pqr" , "xyz" };
ではなく
char
s[3] = { "abc" , "pqr" , "xyz" };
とすれば、

「char* を 3つ持った 配列 s」と読めるんだが。

投稿2021/02/07 11:47

episteme

総合スコア16612

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2021/02/07 12:39

空白なしに char*s[3]; すらもOKだし、 (今回の話から逸れるけれど) char *s1[3],*s2[3]; とか char *s1[3],(*s2)[3]; なんていう記法もあり、というのがなんともカオス...
guest

0

4点目は、*s[3]ではなく、s[3]と宣言すると、s[0]=a,s[1]=b,s[2]=cの様に代入されてしまうのはなぜなのか。

char s[3] = "abc"; と宣言すると、
s[0] の値が 'a'、s[1] の値が 'b'、s[2] の値が 'c' になるのはなぜか、
という質問ですね。

int a[3] = { 3, 5, 7 }; と宣言すると、
a[0] の値が 3、a[1] の値が 5、a[2] の値が 7 になるのは分かりますか?
a[0] の型は int です。a[1]、a[2] の型も int です。

char s[3] = { 'a', 'b', 'c' }; と宣言すると、
s[0] の値が 'a'、s[1] の値が 'b'、s[2] の値が 'c' になるのは分かりますか?
s[0] の型は char です。s[1]、s[2] の型も char です。

char s[3] = "abc";char s[3] = { 'a', 'b', 'c' };
C の宣言としては同じ意味です。(C++ では異なります)

`char *s[3] = { "abc", "pqr", "xyz" }; と宣言すると、
s[0] の値は、文字列 "abc" のアドレスです。
s[1] の値は、文字列 "pqr" のアドレスです。
s[2] の値は、文字列 "xyz" のアドレスです。
s[0] の型は char * です。s[1]、s[2] の型も char * です。
型がポインタだから、その型の変数の値はアドレスです。

投稿2021/02/10 12:32

kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

C言語の変数は、マッチ箱だと思ってください。
int型の変数はマッチ箱の大きさがsizeof(int)、char型の変数はマッチ箱の大きさがsizeof(char)というようになっています。
配列はマッチ箱が並んだものです。
int x[4];
ならば、sizeof(int)の大きさのマッチ箱が4個並んでいます。
intとかcharとかdoubleのようなものは、整数、文字、倍精度浮動小数がそのマッチ箱の中に入ります。
このマッチ箱は、広い部屋のどこかに置いてあります。その置いてある場所のことをアドレスといいます。
アドレスが必要になるときがあるので、アドレスの書いた紙をマッチ箱にしまっておくということもできます。
アドレスの書いた紙の入ったマッチ箱のことをポインタといいます。
int型のマッチ箱の場所の書いた紙を入れておくマッチ箱をint型へのポインタといいます。
ポインタというマッチ箱の大きさは、64ビットOSでは8バイト、32ビットOSでは4バイトなのが普通です。
ポインタの名前がpだとすると、pにはいっているアドレスの先のマッチ箱を*pと書きます。

char s[3]はマッチ箱が3個並んでいて、そのそれぞれの中身はchar型のマッチ箱のアドレスです。
分かりにくいですね。
s[0]、s[1]、s[2]という三つのマッチ箱が並んだものの全体にsという名前を付けました。
s[0]に入っているのはchar型のアドレスの入った紙、つまりポインタです。その先には'a'という文字が入ったマッチ箱があります。そのあとには、'b'、'c'、'\0'という文字が入った箱があります。こういうのがs[1]、s[2]にもありますよ、と言っています。

char **pps=NULL;
は、ppsというマッチ箱を準備しておいてください、という意味です。
pps = s;
とすると、ppsには、sというマッチ箱の最初のものであるs[0]のアドレスを書いた紙が入ります。この紙に書かれたアドレスの先はs[0]なのです、*ppsはs[0]になります。
*ppsは文字へのポインタなので、printfの%sに対応する引数にいれてうまく動きます。

というようなことをまず理解していてください。

投稿2021/02/07 13:27

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ポインタとはという概念をこういうところで説明するのは大変に難しいので、
https://www.amazon.co.jp/dp/477419381X/
などを読んだ方が学習効率がよいと思います。

投稿2021/02/07 12:38

68user

総合スコア2022

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

個別の質問に答えるというよりも、複数ある「*」を混同してとらえていることがコードの理解の妨げになっていると思われるのでそこを指摘したいと思います。

変数宣言に付く「」は変数の型がポインタであることを示す宣言の一部です。
変数を使うときに付く「
」はポインタ変数からその実体を取り出す演算子です。
(ついでにいうと配列の宣言に付く[]と、配列変数の個別要素アクセス時に付く配列アクセス演算子[]も区別して理解する必要があります。)

以下は変数宣言です。上記は「*s」とか「*s[3]」という変数の宣言ととらえるのではなく、sという変数の宣言でその型がポインタ型変数の要素数3の配列であることを宣言しています。

c

1 char *s[3] = { "abc" , "pqr" , "xyz" };

こちらのほうは変数sから必要な情報を取り出しています。
s[i]は、sの配列のi番目のアクセスを意味します。

c

1 printf("%s\n",s[i]);

以下は変数宣言です。**ppsという変数の宣言ととらえるのではなく、ppsという変数の宣言でその型がcharポインタ型を指すポインタ型の変数であることを宣言しています。

c

1 char **pps=NULL;

こちらは変数ppsのアクセスです。ppsに「*」を付けてポインタ変数からその実体を取り出しています。その型はcharポインタ型です。

c

1 printf("%s\n",*pps);

こちらも変数ppsのアクセスです。charポインタのポインタ型にデータサイズ1要素分(今どきのPCだと8バイト)ポインタを後ろにずらすという操作です。

c

1 pps++;

#※コメントを受けて追記

なぜ、ポインタの配列のsがアドレスを持っているのかわかりません。
int t[3]
s = &t
こんな感じでアドレスを入力しなくても大丈夫なのでしょうか?

配列にアドレスを与えることは出来ません。

変数を配列として宣言することとと、ポインタ変数を宣言することでは確保されるデータ構造が違います。

配列として宣言する場合、なにもしなくてもその配列変数を格納するのに必要な領域が確保されアドレスが確定するので、アドレスを与える必要が無く、逆にアドレスを変更したいと思っても変更することは出来ません。

ポインタ変数ならば、初期化をしないとアドレスが不定なのであらかじめ有効な領域のアドレスを与えてあげる必要があります。

Cの場合、ポインタと配列は同じような演算が行えることもあり両者を混同してしまいがちかもしれませんが、明確に違うデータ構造であることを理解して区別することが必要です。

投稿2021/02/07 10:45

編集2021/02/07 13:00
hidezzz

総合スコア1248

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

pepperleaf

2021/02/07 11:02

> charポインタのポインタ型の変数値に1を足して ここ、間違えやすいですね。 charへのポインタの場合、+1 は 1の加算ですが、ポインタのポインタの場合、ポインタ分(64bitだと、 8)の加算。
hidezzz

2021/02/07 11:05

ご指摘ありがとうございます。 そうですね。「1を足す」と書くとそのように誤解されてしまいますね。表現を修正したいと思います。
tsuyoku-naritai

2021/02/07 11:07

回答ありがとうございます。 もう一度ポインタを復習して、「*」について勘違いしていたことがわかりました。なぜ、ポインタの配列のsがアドレスを持っているのかわかりません。 int t[3] s = &t こんな感じでアドレスを入力しなくても大丈夫なのでしょうか?
hidezzz

2021/02/07 12:57

回答に追記しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問