ポインタについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
困ったさん

ポインタについて

#1

投稿記事 by 困ったさん » 18年前

こんにちは。
大学の課題で「整数型変数a,b,cを定義し、それぞれに10,100,1000を代入する。次に整数型ポインタ変数pを
定義し、cのアドレスを代入する。pの値をp+0からp+9まで変化させて、*pの値を画面表示するプログラムを
作成しなさい。」というものをやっているのですが・・・これの実行結果はどのようなものになるでしょうか?

box

Re:ポインタについて

#2

投稿記事 by box » 18年前

p+0のときの*pの値は1000(=cの値)ですが、p+1~p+9のときの*pの値は
ゴミ(何が入っているかわからない)です。

出題の意図が理解できないです。

box

Re:ポインタについて

#3

投稿記事 by box » 18年前

もしかすると、*(p+1)~*(p+9)の値はゴミである、
ということを理解させるための課題かもしれませんね。

困ったさん

Re:ポインタについて

#4

投稿記事 by 困ったさん » 18年前

よくわからないんですが、何が入っているか分からないということは具体的な表示は
ないということですか?

box

Re:ポインタについて

#5

投稿記事 by box » 18年前

printfで表示すれば何かの値を表示します。
しかし、今回は*(p+1)~*(p+9)の値はゴミなので、
訳のわからない値である、ということです。

以下のコードを実行してみてください。
#include <stdio.h>

int main(void)
{
	int a = 10, b = 100, c = 1000, i;
	int *p = &c;
	
	for (i = 0; i <= 9; i++) {
		p += i;
		printf("*p=%d\n", *p);
	}
	return 0;
}

困ったさん

Re:ポインタについて

#6

投稿記事 by 困ったさん » 18年前

なるほどp+0以外のときは本当にむちゃくちゃなんですね。
これはどういうことなんだろう・・・??

keichan

Re:ポインタについて

#7

投稿記事 by keichan » 18年前

プログラム内で確保した領域外のメモリアドレスを指していますので当然ですね。
プログラムで値を代入していない=何が入っているか不定
ということです。

困ったさん

Re:ポインタについて

#8

投稿記事 by 困ったさん » 18年前

わかりました。
もう1問ポインタで聞きたいことがあるのですが、それはまた別にスレを立てさせてもらいます。
みなさんありがとうございました。

困ったさん

Re:ポインタについて

#9

投稿記事 by 困ったさん » 18年前

度々すいません(>_<)なぜp+1以降の値がゴミになるのか考察しなければならないんですけど
なぜゴミになるのかわかりやすく教えてもらえないでしょうか??

keichan

Re:ポインタについて

#10

投稿記事 by keichan » 18年前

今回はint型の変数cを定義していますよね。

その変数cはメモリのどこかに4バイト分確保されます。

例えばcが10000番地~10003番地で確保されているとします。
int *p = &c;
上記コードで pには10000が格納されることになります。

そして p+1 とすると結果は 10004 となるのは理解できますか?

10004番地にはプログラム内の別の変数が割り当てられているか

または全く割り当てられていない所なのかはわかりません。

つまり10004番地以降(10004, 10008, 1000C...)には不定な値(ゴミ)

が入っています。

要はcで確保したメモリ領域以降のアドレスに対して本来"やってはいけない"処理をしているということです。

困ったさん

Re:ポインタについて

#11

投稿記事 by 困ったさん » 18年前

なるほど。確保した領域を超えてしまったために正確な値が表示されることはないということですね?
わかりやすかったです!ありがとうございます。

box

Re:ポインタについて

#12

投稿記事 by box » 18年前

ポインタ変数pの値をp+0~p+9の範囲で変化させて、
そのときの*pの値を参照しています。
これは、*(p+0)~*(p+9)の値を参照しているのです。
ここで、*(p+n)という表記は、一般にp[n]と同じです。
つまり、あたかも配列の要素を参照しているかのごとく
振る舞っています。
ということは、p[0]~p[9]という、p[10]と定義した
配列の要素を参照しているのと実質的に同じです。
くだんのソースコードでは、p[0]にはcの値(1000)が
入っていますが、p[1]~p[9]には明示的に値を
代入していません。これは、p[10]と定義した配列の
先頭要素だけに値をセットしているのと同じで、
先頭要素以外には何が入っているかわかりません。
何が入っているかわからない場所の値を出力するから、
訳がわからない値が出てしまったのです。

box

Re:ポインタについて

#13

投稿記事 by box » 18年前

boxの先ほどの回答は誤りを含んでいます。
ソースの中でp[10]という配列に相当する
領域を確保しているわけではありませんので、
議論そのものが成立しません。
boxの回答は無視してください。

困ったさん

Re:ポインタについて

#14

投稿記事 by 困ったさん » 18年前

いえいえ。boxさんお答えしてくださいましてありがとうございましたm(__)m

なぎ

Re:ポインタについて

#15

投稿記事 by なぎ » 18年前

上に上げられている、box さんのソースですが、一見すると、*(p + 1) // i じゃなくて、数字の 1
は、たまたま、変数 i をポイントするので、ここだけは、まともな値(具体的には、1)が表示されるのではないでしょうか……という疑問がわくのではと思いますが。

あのソースだと、*(p + 0) 以外は、でたらめな値になることが多いようですね。

これは、文法の話というよりは、具体的な処理系の動きになってしまうのですが、上述のソースだと、まず、a, b は、値が代入されただけで全く使われていない、故に、この変数自体が「なかったこと」にされてしまう。

変数 i は、使われているが、アドレスを通して参照されているわけでもないし、変数の数が少ないので、メモリ上には確保されない(やってみたら、レジスタに確保されていました)

変数 c だけは、アドレスを通して参照されているので、メモリ上に確保されている。

というように、実際の挙動がまた、「最適化」というもののおかげで、いろいろ予期しない動きをしてくれたりもします。

box

Re:ポインタについて

#16

投稿記事 by box » 18年前

> 上に上げられている、box さんのソースですが、一見すると、*(p + 1) // i じゃなくて、数字の 1
> は、たまたま、変数 i をポイントするので、ここだけは、まともな値(具体的には、1)が表示されるのではないでしょうか……という疑問がわくのではと思いますが。

boxのところでは、こんなコードを実行して下記の結果を得ました。
iのアドレスはp~p+9とは重なりませんでした。まあ、偶然でしょうけど。
#include <stdio.h>

int main(void)
{
	int a = 10, b = 100, c = 1000, i;
	int *p = &c;
	
	printf("&i=%p\n", &i);
	for (i = 0; i <= 9; i++) {
		p += i;
		printf("i=%d p=%p *p=%d\n", i, p, *p);
	}
	return 0;
}
【実行結果】
&i=0012FF84
i=0 p=0012FF88 *p=1000
i=1 p=0012FF8C *p=1245112
i=2 p=0012FF94 *p=1
i=3 p=0012FFA0 *p=-1
i=4 p=0012FFB0 *p=1245152
i=5 p=0012FFC4 *p=2011662757
i=6 p=0012FFDC *p=2011529823
i=7 p=0012FFF8 *p=4198400
i=8 p=00130018 *p=1048576
i=9 p=0013003C *p=0

box

Re:ポインタについて

#17

投稿記事 by box » 18年前

先ほどの、アドレスを出力するコードの実行結果を見て、
ものすごいバグに気付いてしまいました。
ループの中でpに0を足して1を足して2を足して、...9を足して、
最終的には45要素分先を見てしまっていました。
いや、お恥ずかしい。何をやってたんでしょうか…。
#include <stdio.h>

int main(void)
{
	int a = 10, b = 100, c = 1000, i;
	int *p = &c;
	
	printf("&i=%p\n", &i);
	for (i = 0; i <= 9; i++) {
		if (i != 0) p++;		/* ここが間違っていた! */
		printf("i=%d p=%p *p=%d\n", i, p, *p);
	}
	return 0;
}
【実行結果】
&i=0012FF84
i=0 p=0012FF88 *p=1000
i=1 p=0012FF8C *p=1245112
i=2 p=0012FF90 *p=4226254
i=3 p=0012FF94 *p=1
i=4 p=0012FF98 *p=20783560
i=5 p=0012FF9C *p=20784880
i=6 p=0012FFA0 *p=-1
i=7 p=0012FFA4 *p=1242452
i=8 p=0012FFA8 *p=2147348480
i=9 p=0012FFAC *p=2147348480

なぎ

Re:ポインタについて

#18

投稿記事 by なぎ » 18年前

雑談の追加になりますが……。

box さんの、最後のソースでは、
printf("&i=%p\n", &i);
で、&i が参照されているために、i は、メモリ上に確保されてしまいます。
そして、これがまた、ちょっとおもしろいのは、i は、c より「前」に確保されてしまうのですね。

ですから、
printf("&a=%p\n", &a);
printf("&b=%p\n", &b);
というコードを入れるだけで(こちらは、c より後ろにとられたりするので)
*(p + 0) -> c
*(p + 1) -> b
*(p + 2) -> a
を「たまたま」ポイントして、ここまでは、まともな値が出てきたりします。

このあたりも、コンパイラによって挙動が異なってきます。

「ゴミデータの表示」とはいえ、
printf("&a=%p\n", &a);
printf("&b=%p\n", &b);
の有無で挙動が異なってくるというのは、なかなか、パニックの元だと思います。
というわけで、ポインタがらみのバグは、最適化が絡むと、最悪の状態になります。

#しかし、最初のコードのバグには気づきませんでした、はい。

box

Re:ポインタについて

#19

投稿記事 by box » 18年前

&iを参照しているからではなく、
int i;
で定義した時点でメモリ中の
どこかに配置します。

なぎ

Re:ポインタについて

#20

投稿記事 by なぎ » 18年前

> &iを参照しているからではなく、
> int i;
> で定義した時点でメモリ中の
> どこかに配置します。

それがですね、実は、C言語の規約上、「変数はメモリ上に存在する」というのは保証されていないわけです。
また、たいていのCPUは、メモリ上にアクセスするより、CPUの中のレジスタにアクセスするほうが速いわけです。
このために、「最適化」という手法で、宣言された変数がメモリ上に存在していないことはままあるわけです。
(変数をレジスタにおいてねという意思表示のために、 register という予約語も存在しますが)

一方で、&i と、アドレスが参照されてしまうと、この変数はメモリ上に存在している必要があります。

こんな訳で、数行程度のソースで、変数も数個程度であれば、その変数は実はメモリ上に存在していない可能性があります。

例えば、
int i, a, b, c; の順序で変数を定義して(この場合、i は確保されるとしたら、c の後に確保される可能性が高くなるので)&i という参照の有無で、 *(p + 1) の値がどう変化するかを見ると、&i があれば、*(p + 1) の値がゴミではなく、その時点での i の値になることが確認できるかもしれません。
(コンパイラの動作によるので)

そういう意味で、(このソースの場合は) &i で参照されたがために、メモリ上におかれてしまった という可能性があります。

手元にあった Boralnd の処理系では、&i を参照しない場合、i はメモリ上には存在しませんでした。

box

Re:ポインタについて

#21

投稿記事 by box » 18年前

> それがですね、実は、C言語の規約上、「変数はメモリ上に存在する」というのは保証されていないわけです。

それは全然知りませんでした。

> 手元にあった Boralnd の処理系では、&i を参照しない場合、i はメモリ上には存在しませんでした。

このことをどうすれば確認できますか?

Justy

Re:ポインタについて

#22

投稿記事 by Justy » 18年前

このことをどうすれば確認できますか?
 コンパイル時にアセンブラの結果を出力するようにするか、デバッガでアセンブリ表示させれば確認できますが、機械語を読み解く必要があります。

 コンパイル時の出力は MSVCだとプロパティの「C/C++」「出力ファイル」内に設定項目があります。

なぎ

Re:ポインタについて

#23

投稿記事 by なぎ » 18年前

本当にチェックする場合は、アセンブラによる出力を確認する必要があります。
Borland のコンパイラだと、
bcc32 -S test.c
のように -S をつけると(このオプションは、わりと共通している気がしますが) test.asm というアセンブラのファイルが生成されます。

今回の場合では。

&i による参照がある場合、
?live1@16: ; ESI = &i
となり(この行はコメント)

lea esi,dword ptr [ebp-8]
と、esi レジスタに、[ebp-8] のメモリのアドレスがセットされます。

for() のループの終端で、9と比較する部分は
cmp dword ptr [esi],9
と、[esi] のポイントするメモリにアクセスして比較しています。

一方で、&i がない場合には、

?live1@64: ; EBX = i, ESI = p
となり(この行はコメント)

for() のループ終端で、9と比較する部分は、
cmp ebx,9
と、ebx レジスタが直接使われています。

といった感じです。

もちろん、これは、「コンパイラによってはこういうことになる」ということであって、必ずこうなるという意味ではありません。

box

Re:ポインタについて

#24

投稿記事 by box » 18年前

機械語が読めないので、あきらめます。

閉鎖

“C言語何でも質問掲示板” へ戻る