ポインタについて
Re:ポインタについて
p+0のときの*pの値は1000(=cの値)ですが、p+1~p+9のときの*pの値は
ゴミ(何が入っているかわからない)です。
出題の意図が理解できないです。
ゴミ(何が入っているかわからない)です。
出題の意図が理解できないです。
Re:ポインタについて
printfで表示すれば何かの値を表示します。
しかし、今回は*(p+1)~*(p+9)の値はゴミなので、
訳のわからない値である、ということです。
以下のコードを実行してみてください。
しかし、今回は*(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:ポインタについて
プログラム内で確保した領域外のメモリアドレスを指していますので当然ですね。
プログラムで値を代入していない=何が入っているか不定
ということです。
プログラムで値を代入していない=何が入っているか不定
ということです。
Re:ポインタについて
度々すいません(>_<)なぜp+1以降の値がゴミになるのか考察しなければならないんですけど
なぜゴミになるのかわかりやすく教えてもらえないでしょうか??
なぜゴミになるのかわかりやすく教えてもらえないでしょうか??
Re:ポインタについて
今回はint型の変数cを定義していますよね。
その変数cはメモリのどこかに4バイト分確保されます。
例えばcが10000番地~10003番地で確保されているとします。
int *p = &c;
上記コードで pには10000が格納されることになります。
そして p+1 とすると結果は 10004 となるのは理解できますか?
10004番地にはプログラム内の別の変数が割り当てられているか
または全く割り当てられていない所なのかはわかりません。
つまり10004番地以降(10004, 10008, 1000C...)には不定な値(ゴミ)
が入っています。
要はcで確保したメモリ領域以降のアドレスに対して本来"やってはいけない"処理をしているということです。
その変数cはメモリのどこかに4バイト分確保されます。
例えばcが10000番地~10003番地で確保されているとします。
int *p = &c;
上記コードで pには10000が格納されることになります。
そして p+1 とすると結果は 10004 となるのは理解できますか?
10004番地にはプログラム内の別の変数が割り当てられているか
または全く割り当てられていない所なのかはわかりません。
つまり10004番地以降(10004, 10008, 1000C...)には不定な値(ゴミ)
が入っています。
要はcで確保したメモリ領域以降のアドレスに対して本来"やってはいけない"処理をしているということです。
Re:ポインタについて
ポインタ変数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]と定義した配列の
先頭要素だけに値をセットしているのと同じで、
先頭要素以外には何が入っているかわかりません。
何が入っているかわからない場所の値を出力するから、
訳がわからない値が出てしまったのです。
そのときの*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]と定義した配列の
先頭要素だけに値をセットしているのと同じで、
先頭要素以外には何が入っているかわかりません。
何が入っているかわからない場所の値を出力するから、
訳がわからない値が出てしまったのです。
Re:ポインタについて
boxの先ほどの回答は誤りを含んでいます。
ソースの中でp[10]という配列に相当する
領域を確保しているわけではありませんので、
議論そのものが成立しません。
boxの回答は無視してください。
ソースの中でp[10]という配列に相当する
領域を確保しているわけではありませんので、
議論そのものが成立しません。
boxの回答は無視してください。
Re:ポインタについて
上に上げられている、box さんのソースですが、一見すると、*(p + 1) // i じゃなくて、数字の 1
は、たまたま、変数 i をポイントするので、ここだけは、まともな値(具体的には、1)が表示されるのではないでしょうか……という疑問がわくのではと思いますが。
あのソースだと、*(p + 0) 以外は、でたらめな値になることが多いようですね。
これは、文法の話というよりは、具体的な処理系の動きになってしまうのですが、上述のソースだと、まず、a, b は、値が代入されただけで全く使われていない、故に、この変数自体が「なかったこと」にされてしまう。
変数 i は、使われているが、アドレスを通して参照されているわけでもないし、変数の数が少ないので、メモリ上には確保されない(やってみたら、レジスタに確保されていました)
変数 c だけは、アドレスを通して参照されているので、メモリ上に確保されている。
というように、実際の挙動がまた、「最適化」というもののおかげで、いろいろ予期しない動きをしてくれたりもします。
は、たまたま、変数 i をポイントするので、ここだけは、まともな値(具体的には、1)が表示されるのではないでしょうか……という疑問がわくのではと思いますが。
あのソースだと、*(p + 0) 以外は、でたらめな値になることが多いようですね。
これは、文法の話というよりは、具体的な処理系の動きになってしまうのですが、上述のソースだと、まず、a, b は、値が代入されただけで全く使われていない、故に、この変数自体が「なかったこと」にされてしまう。
変数 i は、使われているが、アドレスを通して参照されているわけでもないし、変数の数が少ないので、メモリ上には確保されない(やってみたら、レジスタに確保されていました)
変数 c だけは、アドレスを通して参照されているので、メモリ上に確保されている。
というように、実際の挙動がまた、「最適化」というもののおかげで、いろいろ予期しない動きをしてくれたりもします。
Re:ポインタについて
> 上に上げられている、box さんのソースですが、一見すると、*(p + 1) // i じゃなくて、数字の 1
> は、たまたま、変数 i をポイントするので、ここだけは、まともな値(具体的には、1)が表示されるのではないでしょうか……という疑問がわくのではと思いますが。
boxのところでは、こんなコードを実行して下記の結果を得ました。
iのアドレスはp~p+9とは重なりませんでした。まあ、偶然でしょうけど。
> は、たまたま、変数 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
Re:ポインタについて
先ほどの、アドレスを出力するコードの実行結果を見て、
ものすごいバグに気付いてしまいました。
ループの中でpに0を足して1を足して2を足して、...9を足して、
最終的には45要素分先を見てしまっていました。
いや、お恥ずかしい。何をやってたんでしょうか…。
ものすごいバグに気付いてしまいました。
ループの中で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:ポインタについて
雑談の追加になりますが……。
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 さんの、最後のソースでは、
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);
の有無で挙動が異なってくるというのは、なかなか、パニックの元だと思います。
というわけで、ポインタがらみのバグは、最適化が絡むと、最悪の状態になります。
#しかし、最初のコードのバグには気づきませんでした、はい。
Re:ポインタについて
> &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 はメモリ上には存在しませんでした。
> 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 はメモリ上には存在しませんでした。
Re:ポインタについて
> それがですね、実は、C言語の規約上、「変数はメモリ上に存在する」というのは保証されていないわけです。
それは全然知りませんでした。
> 手元にあった Boralnd の処理系では、&i を参照しない場合、i はメモリ上には存在しませんでした。
このことをどうすれば確認できますか?
それは全然知りませんでした。
> 手元にあった Boralnd の処理系では、&i を参照しない場合、i はメモリ上には存在しませんでした。
このことをどうすれば確認できますか?
Re:ポインタについて
>このことをどうすれば確認できますか?
コンパイル時にアセンブラの結果を出力するようにするか、デバッガでアセンブリ表示させれば確認できますが、機械語を読み解く必要があります。
コンパイル時の出力は MSVCだとプロパティの「C/C++」「出力ファイル」内に設定項目があります。
コンパイル時にアセンブラの結果を出力するようにするか、デバッガでアセンブリ表示させれば確認できますが、機械語を読み解く必要があります。
コンパイル時の出力は MSVCだとプロパティの「C/C++」「出力ファイル」内に設定項目があります。
Re:ポインタについて
本当にチェックする場合は、アセンブラによる出力を確認する必要があります。
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 レジスタが直接使われています。
といった感じです。
もちろん、これは、「コンパイラによってはこういうことになる」ということであって、必ずこうなるという意味ではありません。
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 レジスタが直接使われています。
といった感じです。
もちろん、これは、「コンパイラによってはこういうことになる」ということであって、必ずこうなるという意味ではありません。