ポインタ変数

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
wrsvrsk
記事: 11
登録日時: 6年前

ポインタ変数

#1

投稿記事 by wrsvrsk » 5年前

出典はC言語 はじめてのプログラミングという本のプログラムです
このような本を読んでいますので、超初心者となります
それではよろしくお願いします

コード:


//ポインタを利用した簡単なプログラム

#include <stdio.h>

int main()
{
  int a = 10 ;
  int *p ;  //ポインタ変数の宣言

  p = &a ; // aの「場所」を代入している

  printf("aの値は:%d\n", a) ; 
  printf("*pの値は:%d\n", *p) ;

  *p -=3 ;

  printf("aの値は:%d\n", a) ;
  printf("*pの値は:%d\n", *p) ;
}
int *p これはポインタ変数の宣言だからアスタリスクがあるのは分かります
p = &a のpがポインタ変数p*ではなく、普通の変数pのように書かれるのはなぜでしょうか?
p*ではエラーになります

また
printf("*pの値は:%d\n", *p) ;
ここの*pは変数の場所ではなく、変数の場所の中身を示すことになる
そうですが、
int *p ; //ポインタ変数の宣言
ここでは変数の場所を示していたのに、唐突な気がします

私の中ではごちゃごちゃしていて丸暗記するしかないのですが、なにか一貫した説明は可能でしょうか?よろしくお願いします

ぴえろう
記事: 2
登録日時: 5年前

Re: ポインタ変数

#2

投稿記事 by ぴえろう » 5年前

int *p;
の意味ですが、「int *」という型の「p」という変数を作っているのです。
「int *」は、「int型変数のアドレスを入れる型」ということです。

なので、
「p」に「aのアドレス」を入れるという意味で
p = &a;
になるのです。


printf("*pの値は:%d\n", *p);
ですが、*pに出てくる「*」と、int *の「*」では意味が違うのです。
int *はあくまで、「int *」がひと塊で、型の名前です。
*pの「*」は演算子で、「そのアドレスの中身を指す」という演算子です。
なので、当然、*の次に来る変数の中身はアドレスでないといけません。

なので、
*p
は、pに入っているアドレスが指す先
ということになるのです。

結城紬
記事: 42
登録日時: 6年前

Re: ポインタ変数

#3

投稿記事 by 結城紬 » 5年前

wrsvrsk さん
C言語のその部分の文法は、分かりにくいことで悪名高く、昔から批判されています。
ぴえろうさんの言う通り、「変数宣言の *」 と「式の中に出てくる *」は(どちらもポインタに関係する、という以外は)全く別の意味を表しており、たまたま同じ記号を使っているに過ぎない、と覚えるのがなんとか一貫した説明になります。
「違う意味なのに同じ記号を使うなんて一貫性がないではないか」と言われれば、まさしく仰る通りですとしか言いようがありません。
なんとも覚えにくい規則で、初心者はほぼ確実に引っかかる必殺の初心者殺しですが、何十年も前から決まっていることなので今更変えることができないのです。
勉強頑張ってください。

box
記事: 2002
登録日時: 13年前

Re: ポインタ変数

#4

投稿記事 by box » 5年前

ポインター変数の書き方が
p*
というのは誤解があります。そういう書き方はありません。
int *p;
と定義した場合、「int *」型、つまりint型を指すポインター変数(中身はint型の何かの変数のアドレス)がpです。
よって、p = &p;
という書き方は真っ当です。
次に
*p
という書き方ですが、これは
int *p;
という定義と関連づけて、
「int」型が*pだという風に理解したらどうでしょう。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

wrsvrsk
記事: 11
登録日時: 6年前

Re: ポインタ変数

#5

投稿記事 by wrsvrsk » 5年前

ぴえろう様
>int *p;
の意味ですが、「int *」という型の「p」という変数を作っているのです。
「int *」は、「int型変数のアドレスを入れる型」ということです。
------
非常に丁寧に書かれているテキストではありますが、このような誤解をさける説明がないので驚いています。ここで質問しておいて良かったです。
テキストではintと*の間を入れてますので、余計に*pでひと固まりだと誤解してしまいます。ちなみにint* pでも正常に作動しましたが、これはコンピュータが補正して読み込んでるのか、どちらでも正しいのかどっちなんでしょうかね
よそさまのページによりますた変数が2つ以上だと、2つ目以降は普通のint型変数になってしまうらしいですね。これ以上深入りするとわけが分からなくなりそうなので、int *pと憶えておくと良いかもしれません
強いて言えば*pこのpはint型変数のアドレスが入ってるpだよって意味であって*pという変数があるわけではなく、pはあくまでもpという事でしょうか

>「p」に「aのアドレス」を入れるという意味で
p = &a;
になるのです
-----
なるほど、一瞬なんでa=アドレスにすれば、&なんていらんじゃないかと思いましたが、そのアドレスが分からないんですものね

>*pの「*」は演算子で、「そのアドレスの中身を指す」という演算子です。
なので、当然、*の次に来る変数の中身はアドレスでないといけません。
---
なるほど、よく分かりました。

もやもやが解けて本当に助かりました。ありがとうございました。それ以外にお答えいただいた結城さん、boxさんも感謝です

wrsvrsk
記事: 11
登録日時: 6年前

Re: ポインタ変数

#6

投稿記事 by wrsvrsk » 5年前

ただちょっと疑問がありまして、int型変数のアドレスもintに内包されるので
int p
として
p = &a
でaの場所を代入
printf("*pの値は:%d\n", *p) ;
ここでpの値をアドレスと解釈してその中身を見ろと
指令出来ないものですかね
実際これでやるとエラーと出るのでpをint型変数のアドレスだと事前に指定しないと、pにいくらint型変数の正しいアドレスを入れても、コンピューターはアドレスとして解釈してくれないし、その中身も見てくれないということでしょうか?

wrsvrsk
記事: 11
登録日時: 6年前

Re: ポインタ変数

#7

投稿記事 by wrsvrsk » 5年前

int a = 10 ;
int *p ; //ポインタ変数の宣言
p = &a ; // aの「場所」を代入している
printf("pの値は:%d\n", p) ;
にすると普通にアドレスらしき数字6433212が表示されますが

これをint pに変えた時点でエラーになります
int a = 10 ;
int *p ; //ポインタ変数の宣言
p = &a ; // aの「場所」を代入している
このプログラムだけでもアセンブリ出来るのに対し
int pに変えただけでエラーとなりますので、やはり
p = &a の時点ではねられている、エラーになっているとみるべきですね。pをint型変数を入れる変数型としないと、アドレスを読み込めない仕様となっているようです。

アバター
usao
記事: 1887
登録日時: 11年前

Re: ポインタ変数

#8

投稿記事 by usao » 5年前

何が言いたいのか(やりたいのか)いまいち不明ですが,コンパイルエラーなのであれば
エラーの箇所と内容(原因)に関するエラーメッセージが出てるでしょう.
「int* から int には変換できない」的な内容が出てませんか?

int型 と int*型 とは 異なる型 なのであるからして,
異なる型の物を代入しようとする記述がエラーになっている
というだけの話ではないのですか?

コード:

typedef struct SA{ 略 } SA;

SA sa;
float f;
f = sa;  //SA型をfloat型に代入したいです
これ↑で「エラーが出る! 不思議!」とか言ってるのと一緒.

で,何が何でも int型のpに &aの値を代入しなければならないのであれば

コード:

p = (int)( &a );  //キャストでGO
とかして,型をintに変換してやればとりあえずコンパイル通るかと.

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ポインタ変数

#9

投稿記事 by ISLe » 5年前

wrsvrsk さんが書きました:
5年前
ただちょっと疑問がありまして、int型変数のアドレスもintに内包されるので
64ビット実行環境だとintが32ビットでアドレスが64ビットだったりすることがあります。

C/C++言語において、アドレスの表現は、コンパイラ側で勝手に決めていいということ以外は規定がありません。

あと、アドレスをprintfの%dで表示するということにおいては、PCがぶっ飛んでも不思議ではないことだというふうに定義されています。
まあ、たいていは、コンパイラなりOSなりが、PCがぶっ飛んだりするような大事には至らないように対処してくれます。OSが起動しなくなるくらいはたまにあることですが。

%pで表示するにしてもその形式はコンパイラ依存なので、まじめに中身のビット列を見たいならchar配列に格納して1バイトずつ表示しましょう。
charは内部表現を保持できる型として保証があります。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ポインタ変数

#10

投稿記事 by ISLe » 5年前

オフトピック
前言時の*はポインタ宣言子。変数名(直接宣言子)には含まれない。
int i, *p, **pp;
iは、『int』という型の値を保持できる変数。
pは、『int』+『へのポインタ』という型の値を保持できる変数。
ppは、『int』+『へのポインタへのポインタ』という型の値を保持できる変数。

iを使って、intという型の値を『直接』やりとりできる。
pを使って、intへのポインタという型の値を『直接』やりとりできる。
ppを使って、intへのポインタへのポインタという型の値を『直接』やりとりできる。

これらが一時変数であった場合…
初期化されていないiを読み出そうとすると、未定義の動作となる。
初期化されていないpを読み出そうとすると、未定義の動作となる。
初期化されていないppを読み出そうとすると、未定義の動作となる。

変数の扱いに関しては、intとcharやdoubleの違いとかと大して変わらないのだけれど、ポインタ変数の読み書きという話題が出ると、「アドレスを格納してないポインタ変数を参照しちゃいかん」と脊髄反射的に騒ぎ出すひとがしばしば現れる。
だったら、iもpもppも未初期化の一時変数だとして
pp=&p;
p=&i;
に何の問題もないことをどう説明するの。

ポインタを使った間接参照があたかも直接参照と排他であるかのようなミスリードを誘う解説が世の中にあふれているのが混乱の原因だと思う。さらに、そもそもポインタという存在に非があるかのようなポインタに対する否定的ムードが形成されていることがこの問題を長引かせていると思う。

TKS

Re: ポインタ変数

#11

投稿記事 by TKS » 5年前

>ぴえろうさんの言う通り、「変数宣言の *」 と「式の中に出てくる *」は(どちらもポインタに関係する、という以外は)全く別の意味を表しており、たまたま同じ記号を使っているに過ぎない、と覚えるのがなんとか一貫した説明になります。
「*p」は、アドレスpが指す先を示す記号として一貫性が保たれています。

「int* p」は「*p」(アドレスpの指す先)がint型である宣言。
式中に「*p」が出て来たとしても、それは同じくアドレスpが指す先です。

同じ記号で違う意味を持つ例としては、乗算演算子「*」の方ですね。

かずま

Re: ポインタ変数

#12

投稿記事 by かずま » 5年前

ISLe さんが書きました:
5年前
オフトピック
前言時の*はポインタ宣言子。変数名(直接宣言子)には含まれない。
「前言時」は「宣言時」のタイポだとして、
* は、ポインタ宣言子ではありません。
* が変数名(直接宣言子)に含まれないのは当然です。

以下、JIS X 3010:2003 (ISO/IEC 9899:1999) からの抜粋です。

コード:

6.7 宣言
構文規則
	宣言:
		宣言指定子列 初期化宣言子並びopt ;
	宣言指定子列:
		記憶域クラス指定子 宣言指定子列opt
		型指定子 宣言指定子列opt
		型修飾子 宣言指定子列opt
		関数指定子 宣言指定子列opt
	初期化宣言子並び:
		初期化宣言子
		初期化宣言子並び , 初期化宣言子
	初期化宣言子:
		宣言子
		宣言子 = 初期化子

コード:

6.7.5 宣言子
構文規則
	宣言子:
		ポインタopt 直接宣言子
	直接宣言子:
		識別子
		( 宣言子 )
		直接宣言子 [ 型修飾子並びopt 代入式opt ]
		直接宣言子 [ static 型修飾子並びopt 代入式 ]
		直接宣言子 [ 型修飾子並び static 代入式 ]
		直接宣言子 [ 型修飾子並びopt * ]
		直接宣言子 ( 仮引数型並び )
		直接宣言子 ( 識別子並びopt )
	ポインタ:
		* 型修飾子並びopt
		* 型修飾子並びopt ポインタ

コード:

6.7.5.1 ポインタ宣言子
6.7.5.2 配列宣言子
6.7.5.3 関数宣言子(関数原型を含む)
以上から、
int i, *p, **pp; という宣言があった時、
int は、宣言指定子であり、型指定子です。
i は、宣言子であり、直接宣言子であり、識別子(変数名)です。
*p は、宣言子であり、ポインタ宣言子です。
**pp は、宣言子であり、ポインタ宣言子です。

* や ** は、ポインタ宣言子ではありません。
*p の p や、**pp の pp は、直接宣言子であり、識別子(変数名)です。

int a[3]; という宣言があった時、
a[3] は、宣言子であり、配列宣言子です。
[3] は、配列宣言子ではありません。
a[3] の a は、直接宣言子であり、識別子(変数名)です。
ISLe さんが書きました:
5年前
オフトピック
変数の扱いに関しては、intとcharやdoubleの違いとかと大して変わらないのだけれど、ポインタ変数の読み書きという話題が出ると、「アドレスを格納してないポインタ変数を参照しちゃいかん」と脊髄反射的に騒ぎ出すひとがしばしば現れる。
だったら、iもpもppも未初期化の一時変数だとして
pp=&p;
p=&i;
に何の問題もないことをどう説明するの。
脊髄反射的に騒ぎ出す人は、「アドレスを格納してないポインタ変数を
使って、ポインタの指している先のオブジェクトを参照しちゃいかん」と
言っているつもりなんでしょう。

int i, *p, **pp; という宣言があった時、
p = &i; や pp = &p; は何も問題ないが、
*p = i; や i = *p; という使い方をしちゃいかん、
と言っているのでしょう。

代入演算子= の左オペランドや、単項演算子& のオペランドは、
未初期化の一時変数であっても何も問題ありません。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: ポインタ変数

#13

投稿記事 by ISLe » 5年前

>かずまさん
ご指摘ありがとうございます。
宣言に現れる*単体の名称は存在しないわけですね。

かずま さんが書きました:
5年前
脊髄反射的に騒ぎ出す人は、「アドレスを格納してないポインタ変数を
使って、ポインタの指している先のオブジェクトを参照しちゃいかん」と
言っているつもりなんでしょう。

int i, *p, **pp; という宣言があった時、
p = &i; や pp = &p; は何も問題ないが、
*p = i; や i = *p; という使い方をしちゃいかん、
と言っているのでしょう。

代入演算子= の左オペランドや、単項演算子& のオペランドは、
未初期化の一時変数であっても何も問題ありません。
そうですね。

どうして、ポインタの指している先のオブジェクトの話なんて一切してないときに、ポインタの指している先のオブジェクトの話を持ち出すひとが、あとからあとから湧いて出るのか、というのを気にしてるんですけどね。

とうぜん話が噛み合うはずもなく紛糾して、それがポインタが難解だというイメージに結び付けられて何もいいことない。

ぴえろう
記事: 2
登録日時: 5年前

Re: ポインタ変数

#14

投稿記事 by ぴえろう » 5年前

> ただちょっと疑問がありまして、int型変数のアドレスもintに内包されるので
> int p
> として
> p = &a
> でaの場所を代入
> printf("*pの値は:%d\n", *p) ;
> ここでpの値をアドレスと解釈してその中身を見ろと
> 指令出来ないものですかね

できます。
具体的には、

コード:

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

int main( int iArgc, char *pcArgv[] )
{
    int a = 10;
    int p;

    p = ( int )( &a );

    printf( "a is %d\n", *( ( int * )p ) );

    return 0;
}
という感じになります。

ポイントとしては、

p = ( int )( &a );

「intの値」が入るpには、「int型変数のアドレス」は入れられません。
同じ数値でも、intの1と、アドレスの1は、意味が違うので、そのままでは入れられません。

なので、「&a」の型は、本当は「int *」だけど、「int」に変えてという意味で、 ( int )でキャストしてpに代入しています。

次に、printf()内の
*( ( int * )p )
の部分ですが、pの型は、「int」だけど、アドレスの数値が入っているので、「int *」に変えてという意味で、( int * )でキャストすることで、「( int * )p」の部分が、「int *」型になるので、あとは、そのアドレスに入っている値を出す演算子の「*」を付けて、*( ( int * )p )にしています。

かずま

Re: ポインタ変数

#15

投稿記事 by かずま » 5年前

ぴえろう さんが書きました:
5年前
できます。
具体的には、
コンパイル環境(OS やコンパイラ)は何ですか?

そのプログラムが動かない環境が増えつつあります。

Ubuntu の gcc や、Cygwin の gcc は 64ビットコンパイラになっていて、
sizeof(int *) = 8
sizeof(int) = 4
なので、ポインタを int にキャストした時、64ビットのポインタの
上位32ビットの値がなくなります。int * へのキャストでポインタに
戻しても、元のアドレスは復元されず、そのプログラムは落ちます。

VC++ を Visual Studio で使っているのなら、
ツールバーの x86 を x64 に変更してビルドしてみてください。
やはり、そのプログラムは動きません。

返信

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