ちょっと行き詰まっています。
苦しんで覚える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で返ってくるのは、ポインタ変数(の配列)だと思うので、変数のモードを切り替えるためにアスタリスクが必要なのだと思っていましたが、どこかで重大な勘違いをしているようです。
この件について、どなたか教えていただけないでしょうか。
No.1ベストアンサー
- 回答日時:
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という値を出力しているように見えます。が、これはたまたまであって、他の場合に同じようになるとは限りません。
ありがとうございます。
お礼が遅れましたが、回答はすぐに見ておりました。
試しに、次のように書いてみました。
int array[3] = {5,6,7};
printf("%d\n",*(array +1));
printf("%d\n",array[1]);
すると、結果は同じ「6」になりました!
配列の変数は、単体では最初の配列のアドレスで、[]の添字でそれをずらしていたんですね。
そうなってくると、別の回答でも指摘された「ポインタ変数の配列」という解釈そのものがそもそもおかしいと言うことがわかりました。
> *(heap[5])
> となります。
> heap[5] = *(heap+5) より、int型となり、ポインタの演算*が使えないことがわかります。
この部分、納得しました!
レベルに合わせて順序立てて説明してくださりありがとうございました!
No.3
- 回答日時:
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]
とします。
ありがとうございます。
>ポインタが入る入れ物です。大きさはsizeof (int*)で大した大きさではありません。
ここですね。たしかに、おっしゃるとおりです。宣言だけを見れば、ポインタのアドレスだけが入る変数ですが、mallocを見ていたら、なんだか配列が入るのではという気がしていました。
mallocの戻り値は、指定された範囲の先頭のアドレス、というだけですね。ヒープという文脈で共起表現のように出てくる「確保」という言葉からなんとなくイメージだけはあったのですが、mallocはまさに「確保」してくれて、その先頭アドレスを教えてくれるだけなんですね。きっと、確保された領域はほかの変数やら何やらで侵されないようなしくみがあるのだと推察します。そう考えるといろいろのことに合点がいきます。
また一歩、レベルアップした気がします!
No.2
- 回答日時:
int *heap;
int i;
があるとき、
heap[i] と *(heap+i) は等価です。
したがって
> 1:printf("%p\n",*heap[5]); //エラー
これが "heap[5] のアドレス" を printしたかったのであれば
&heap[5] または heap+5 が正解。
ありがとうございます。
試みに、次のように書いて実行してみました。
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]));
(ひょっとしたら一部、本当はエラーになるものが含まれているかもしれませんが)
私のコンパイラでは、すべて同一のアドレスが返されました。
配列というのは、[]を使ってポインタの仕組みを利用して配列を実現しているようですが、配列の中の個々の変数自体は、やっぱり普通の変数として機能しているのですね。だからこそ、&をつけてアドレスが出るのですね
大変勉強になりました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# C言語プログラム変更 2 2022/12/21 15:03
- C言語・C++・C# 10個の実数に対する降順ソート結果を出力するプログラムを作りたいのですが、以下のプログラムをどう直せ 1 2022/07/09 22:16
- C言語・C++・C# c言語の問題です 3 2023/01/10 16:15
- C言語・C++・C# c言語の問題です 課題1 (二分探索木とセット) 大きさ size の配列 array を考える。す 2 2023/01/10 21:08
- C言語・C++・C# 至急教えてください! プログラミングの問題です! お願いします! 出力2と全く同じ出力をするように、 2 2022/06/22 23:10
- C言語・C++・C# C言語の課題が出たのですが自力でやっても分かりませんでした。 要素数がnであるint型の配列v2の並 3 2022/11/19 17:41
- C言語・C++・C# このプログラミング誰か教えてくれませんか 1 2022/06/02 15:27
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 malloc関数を使ってください!お願いします! 最 1 2022/07/21 09:28
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分 1 2022/07/19 17:03
- C言語・C++・C# const char** p;のとき、free(p)でC4090エラーとなるのはなぜですか 3 2023/03/31 16:28
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
セグメントエラー
-
init関数の意味
-
fopne で失敗する原因
-
C言語のポインタに直接アドレス...
-
C言語の関数と配列に関する質問
-
main(int argc,char **argv[])...
-
x64プログラムでアドレスが32bi...
-
Run-Time Check Failure #3とい...
-
C言語でのconstを返す関数
-
【C言語】戻り値が構造体の関数
-
[excel vba] マウスポインタの...
-
プログラミングのfarについて
-
NULLとブランクの違い
-
ExcelVBAでのkernel32(64bit)
-
参照型で受け取った引数をポイ...
-
LPSTR型の初期化について
-
DLLからEXEに構造体を渡すとき
-
C言語 関数の戻り値と自動変数
-
DLL<->VB間での受け渡し(文字...
-
C言語で構造体の参照渡しができ...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
セグメントエラー
-
C言語のポインタに直接アドレス...
-
init関数の意味
-
戻り値で構造体を返すことは可...
-
fopne で失敗する原因
-
C言語の関数と配列に関する質問
-
Run-Time Check Failure #3とい...
-
LPSTR型の初期化について
-
ExcelVBAでのkernel32(64bit)
-
main(int argc,char **argv[])...
-
アプリを32bitから64bit移行
-
連結リスト 要素の入れ替え
-
ハンドルはポインタか
-
Cで作成したDLL関数をVBから呼...
-
C言語でのconstを返す関数
-
NULLとブランクの違い
-
エラーの意味
-
DLL<->VB間での受け渡し(文字...
-
ハンドル、アドレス、ポインタ...
-
【C言語】戻り値が構造体の関数
おすすめ情報