プロが教える店舗&オフィスのセキュリティ対策術

ちょっと行き詰まっています。

苦しんで覚えるCで勉強しているのですが、まさに苦しんでいます。
http://9cguide.appspot.com/19-01.html

#include <stdio.h>
#include <stdlib.h>

int main()
{
int i;
int *heap;
heap = (int *)malloc(sizeof(int) * 10);
if (heap == NULL) exit(0);

for (i = 0;i < 10;i++) {
heap[i] = i;
}
printf("%d\n",heap[5]);

free(heap);

return 0;
}

int *heap;
ここで int ポインタを宣言しています。

heap = (int *)malloc(sizeof(int) * 10);
ここでヒープを確保しています。(int *) のキャストも sizeof(int) も理解できました。

for (i = 0;i < 10;i++) {
heap[i] = i;
}

まず1点目の疑問はここです。
変数 heap は「ポインタ変数」です。それでいて配列です。

ポインタ変数は、プログラムの文中で通常の変数として使うときには「*heap」のように先頭にアスタリスクを付けなければならかなったと記憶しています。
アスタリスクなしの「heap」はアドレス格納用の変数ではないでしょうか。

printf("%d\n",heap[5]);

そして、その疑問をよそに、この命令が成り立っているようです。
画面上に出される結果は「5」であり、変数「heap」がただの配列として機能しているように見えます。

この printf 次のように書き換えると、エラーが出てコンパイルできませんでした。

書き換え実験1
printf("%p\n",*heap[5]);
アスタリスクを付けて、通常の変数として扱い、受ける方も「%d」から「%p」に書き換えてアドレスを表示してみようと思ったのですが、
「「pointer」を付け忘れています。」というエラーが表示されました。

書き換え実験2
printf("%p\n",heap[5]);
受ける方を「%d」からポインタを受ける「%p」にしましたが、変数の方はアスタリスクなしです。
すると、結果はアドレス「00000005」が返ってきました。
(変数にアスタがないのになぜ?)

書き換え実験3
printf("%d\n",*heap[5]);
これはもうめちゃくちゃですが、一応やってみました。コンパイルエラーで、
「「pointer」を付け忘れています。」というエラーが表示されました。

つまり、こういうことです。
0:printf("%d\n",heap[5]); //5
1:printf("%p\n",*heap[5]); //エラー
2:printf("%p\n",heap[5]); //00000005
3:printf("%d\n",*heap[5]);//エラー

この結果から推測するに、アスタリスクはそもそも付けるとエラーになり、アドレスを表すか、そのアドレスに格納された値を表すかを切り替えるには、単にその変数を受ける「%d」や「%p」を変えるだけ、ということになるのだと思います。

mallocで返ってくるのは、ポインタ変数(の配列)だと思うので、変数のモードを切り替えるためにアスタリスクが必要なのだと思っていましたが、どこかで重大な勘違いをしているようです。

この件について、どなたか教えていただけないでしょうか。

A 回答 (3件)

C言語では、配列とポインタが密接な関係にあります。



double *p ;
int n ;
としたときに
*(p+n) と p[n] は同じものを指します。 (ポインタpから数えてn番目のアドレスにある、double型)
*p = *(p+0) = p[0] です。

なお、同様に
double a[MAX] ;
int n ;
としたときに
a[n] と *(a+n) は同じものを指します。
a[0] = *(a+0) = *a です。


> ポインタ変数は、プログラムの文中で通常の変数として使うときには「*heap」のように先頭にアスタリスクを付けなければならかなったと記憶しています

* 演算子を付けるかどうかは、やりたいことが何か、と、操作しようとしている値の型は何か、と考えて使うようにしましょう。
ポインタだから*、と単純に考えないことです。

> *heap[5]

こに括弧を付けるすると、演算子の優先順位から[]の方が強いため
*(heap[5])
となります。
heap[5] = *(heap+5) より、int型となり、ポインタの演算*が使えないことがわかります。


> アドレスを表すか、そのアドレスに格納された値を表すかを切り替えるには、単にその変数を受ける「%d」や「%p」を変えるだけ、ということになるのだと思います。

これは、printf(や同系統の関数)の仕様の話になります。
printfでは、引数の型は調べていません。なので「アドレスを表すか、そのアドレスに格納された値を表すか」ということは関係ありません。 heap[5] の値がそのまま渡されます。

これを、書式文字列に従って取り出すだけです。
%dだったらsizeof(int)だけメモリから順番に読み出し、intとして解釈した値を出力します。
%pだったらsizeof(void *)だけメモリから順番に読み出し、ポインタとして解釈した値を出力します。

今回は同じ5という値を出力しているように見えます。が、これはたまたまであって、他の場合に同じようになるとは限りません。
    • good
    • 1
この回答へのお礼

ありがとうございます。
お礼が遅れましたが、回答はすぐに見ておりました。

試しに、次のように書いてみました。

int array[3] = {5,6,7};
printf("%d\n",*(array +1));
printf("%d\n",array[1]);

すると、結果は同じ「6」になりました!
配列の変数は、単体では最初の配列のアドレスで、[]の添字でそれをずらしていたんですね。
そうなってくると、別の回答でも指摘された「ポインタ変数の配列」という解釈そのものがそもそもおかしいと言うことがわかりました。

> *(heap[5])
> となります。
> heap[5] = *(heap+5) より、int型となり、ポインタの演算*が使えないことがわかります。

この部分、納得しました!

レベルに合わせて順序立てて説明してくださりありがとうございました!

お礼日時:2013/02/04 16:09

int *heap;


int i;
があるとき、

heap[i] と *(heap+i) は等価です。

したがって

> 1:printf("%p\n",*heap[5]); //エラー
これが "heap[5] のアドレス" を printしたかったのであれば
&heap[5] または heap+5 が正解。
    • good
    • 0
この回答へのお礼

ありがとうございます。

試みに、次のように書いて実行してみました。

int array[3] = {5,6,7};
printf("%p\n",&*(array +1));
printf("%p\n",&(*(array +1)));
printf("%p\n",&array[1]);
printf("%p\n",&(array[1]));

(ひょっとしたら一部、本当はエラーになるものが含まれているかもしれませんが)
私のコンパイラでは、すべて同一のアドレスが返されました。

配列というのは、[]を使ってポインタの仕組みを利用して配列を実現しているようですが、配列の中の個々の変数自体は、やっぱり普通の変数として機能しているのですね。だからこそ、&をつけてアドレスが出るのですね

大変勉強になりました。

お礼日時:2013/02/04 16:14

int *heap ;


はポインタが入る入れ物です。大きさはsizeof (int*)で大した大きさではありません。メモリアドレスが1つ入る分だけの大きさです。したがってあなたが考えているような、複数のポインタをここに入れるのは無理があります。

heap = (int *)malloc(sizeof(int) * 10);
でmallocはどこにあるかユーザーはわからないけどヒープという大きな領域から連続したsizeof(int) * 10バイトの領域を切り出してあなたに提供してくれました。mallocはあなたがint型のデータを複数格納するために使用するとかは知りません。とにかくあなたに言われたバイト数の領域を単に切り出しただけです。
そしてmallocの戻り値は「ポインタ変数(の配列)」ではなく切り出したsizeof(int) * 10バイトの領域の先頭アドレス1つです。切り出した領域は型の色がついている訳ではありませんのでどのように使おうと勝手なのですが、あなたはint型の配列用に使うために(int*)でキャストしてheapに格納してます。

得られた領域はint型のデータが10個分格納できる大きさです。5番目をアクセスしたければ
heap [5]
とします。
    • good
    • 0
この回答へのお礼

ありがとうございます。

>ポインタが入る入れ物です。大きさはsizeof (int*)で大した大きさではありません。

ここですね。たしかに、おっしゃるとおりです。宣言だけを見れば、ポインタのアドレスだけが入る変数ですが、mallocを見ていたら、なんだか配列が入るのではという気がしていました。

mallocの戻り値は、指定された範囲の先頭のアドレス、というだけですね。ヒープという文脈で共起表現のように出てくる「確保」という言葉からなんとなくイメージだけはあったのですが、mallocはまさに「確保」してくれて、その先頭アドレスを教えてくれるだけなんですね。きっと、確保された領域はほかの変数やら何やらで侵されないようなしくみがあるのだと推察します。そう考えるといろいろのことに合点がいきます。

また一歩、レベルアップした気がします!

お礼日時:2013/02/04 16:19

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!