前提
最近C言語のポインターについて勉強していて、以下のようなことを学びました。
int型の変数a
を定義したとき、
a
はaに入っている値を、&a
はaがあるアドレスを表す。
int型のポインタ*p
を定義したとき、
p
は自身が差しているアドレスを、
*p
は自身が差しているアドレスにある変数の値を表す
さて、以下のソースコードを考えます
c
1#include <stdio.h> 2 3int main(void){ 4 int a = 10; 5 int *p = &a; 6 int *k; 7 k = &a; 8 printf("%d %d",*p,*k);//10 10 9 return 0; 10}
理解できていないこと
ポインタ変数 *k
については理解できます。
まず、 int *k
でint型ポイント変数の*k
を定義して、
つぎの行でaのアドレス&a
を*k
が指すアドレス(k
)に代入。
だから、最後のprintfで*k
を出力すると、
*k
= kが指しているアドレスにある変数(=a
)の値(=10)を出力する。
ところが、*p
について理解ができていません。
プログラムを見ると、*p
を宣言するのと同時に代入を行っています。
int *p = &a;
これを見ると、=の左辺 *p
は、前提より "自身が差しているアドレスにある変数の値"
=の右辺&a
は、前提よりaがあるアドレスを表すことになり、
変数の値にアドレスをぶち込んでいることなります。
これだとコンパイルエラーになるか、printfで変な値が表示されそうですが、
実際にやってみると普通に表示されます。
これはなぜなのでしょうか。
補足情報
C99
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/05/22 21:14 編集

回答8件
0
int *p = &a;
はint *
型の変数p
に&a
を代入するというものです。
言い換えれば
C
1 int *p; 2 p = &a;
とほぼ同じです。
なので、
*pは自身が差しているアドレスにある変数の値を表す
とは違うものです。
投稿2025/05/22 11:45
総合スコア10346
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
すでに多数回答が付いていますが,
「 *p
という記述というのは(一律的に)こういう意味である」という捉え方が間違っているというだけです.
すなわち,
左辺 *p は、前提より "自身が差しているアドレスにある変数の値"
なる「前提」が間違い.
*
とか &
とかがどういう意味になるのかは文脈依存とでもいうか,そういうものなので.
個々の場所でそれらがどういう文法要素として記述されているのかが異なるということです.
……とはいえ,言うてもせいぜい数パターンくらいしかない(?)感じですから,過度に難しく考えることはないです.
投稿2025/05/23 01:12
総合スコア12164
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
すでにある回答と重なる内容ですが、このあたりのCの仕様をちゃんと理解する前は、単純な型以外の変数の宣言と初期代入は、分けて書いた方が良い気がします(と言っても、読む場合はしょうがないですが)。
int 「ここに*やら[]やら()やらを含んだややこしい変数xの記述」= 初期値;
は、初期値はあくまで変数に代入する物なので、int 「ここに*やら[]やら()やらを含んだややこしい変数xの記述」; x = 初期値;
と読みます。
これはもうそう思うしか無い。変数名しか無いint x = 初期値;
とかはそのまま同じですが。
Cの変数宣言が、「この変数の型はこれだ」じゃなくて「この変数にこういう記号をいろいろ付加したときに最終的に得られる値の型はこれだ」になっているのは、ちゃんと調べてませんが、おそらくコンパイラのサイズを小さくするための気がします(変数宣言の構文解析処理と式の構文解析処理の共通化)。
あるいは、実際にその式を評価するのと同じ書き方で宣言できる今の形式の方が分かり易いという判断からかも知れません。今のCでint *((*foo)());
という変数宣言があったとして、もし「型 変数名」という形式の宣言にCの文法を変更すると、int*((*)()) foo;
になりますが、こっちの宣言形式の方が分かり易いという気がしないです。
投稿2025/05/24 17:34
総合スコア86303
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
=の左辺 *p は、前提より "自身が差しているアドレスにある変数の値"
“自身が指しているアドレスにある変数(値)の型”を示すのが左辺の*pです
「*pが指す値はint型だよ」ということです
このことを事前に明示します
だからこその”宣言”です
こうした例はポインタ以外にも適用されます
これはCにおける変数宣言の基本仕様です
int a=0, *b=&a, c(void), d[]={1,2,3}; int c(){return 0;};
取得される値の型が同一であれば宣言は一括で行えます
pもこの規則に従っているだけで、変数宣言時のpが何かの値を直接返す訳ではありません
尚この宣言形式が分かりにくい場合はtypedefを使用してください
typedef int *INTPTR; INTPTR a,b,c; //全ての変数がINTPTR(int *)型になる
投稿2025/05/24 08:43
編集2025/05/24 08:45総合スコア105
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
int *p;
という宣言は、ポインタ変数 p を宣言しています。
そして宣言した結果 p の型は int * 型になります。
int *p = &a;
これは宣言している p を &a つまり a のアドレスで初期化しています。
(細かいことを言うと代入ではありません)
なお、
int *p;
の宣言は(入門書で良く説明されている)
型名 変数名;
という構造ではありません。
型指定子(type-specifier) ポインタ(pointer) 変数名(identifier);
という構造です。
pointer は続く identifier がポインタであることを意味しています。
括弧を付けて書けば、あなたの感覚通り
int (*p);
であって
(int *) p;
ではありません。
(実際に C規格準拠コンパイラで確認して見ればわかるでしょう)
そのため
int *p, q;
では q は int 型になります。
参考までに int *p; における宣言部分の Syntax を提示すると次のようになっています。
declaration: declaration-specifiers init-declarator-list_opt ; declaration-specifiers: type-specifier declaration-specifiers_opt type-specifier: int init-declarator-list: init-declarator init-declarator: declarator declarator: pointer_opt direct-declarator direct-declarator: identifier pointer: * type-qualifier-list_opt
投稿2025/05/23 11:50
総合スコア1180
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
いろいろな回答が付いていますが、僕なりの考えです。
int *p = &a;
この左辺で宣言しているのは、「int型ポインタの*pを定義」ではなく「int型ポインタのpを定義」しています。 変数はあくまで p です。
また 、代入という操作は左辺の値に右辺の値を入れることですから、この式を「*p = &a」と同等ととらえることはできなくて、左辺は「int *p」です。 このとき、「int *p」は宣言した変数であるところのpを返す(表す)と考えれば、整合性が取れるようになります。
まあ、複数並べたときにやっぱり変だよねとは思いますけどね。
投稿2025/05/23 01:54
編集2025/05/23 01:56総合スコア14330
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
あ〜、これ。Cのクソ仕様の話かな。
当然、
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <inttypes.h> 4 5int main(void) 6{ 7 int16_t a = 10; 8 int16_t *p = &a; 9 int16_t *k = &a; 10 printf("%" PRId16 " %" PRId16 "\n", *p, *k); /* 10 10 */ 11 return EXIT_SUCCESS; 12}
と
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <inttypes.h> 4 5int main(void) 6{ 7 int16_t a = 10; 8 int16_t *p = NULL; 9 p = &a; 10 int16_t *k = NULL; 11 k = &a; 12 printf("%" PRId16 " %" PRId16 "\n", *p, *k); /* 10 10 */ 13 return EXIT_SUCCESS; 14}
は同じ結果になる、ってのは分かるかな。
まぁ当たり前だけどさ。
んで、実は(ポインタ変数)宣言時のアスタリスクと使用する際の本体でのアスタリスクの意味は違うんだ。
つまり、
int型のポインタ*pを定義したとき、
pは自身が差しているアドレスを、
*pは自身が差しているアドレスにある変数の値を表す
ってのは、だ。
int型のポインタ*pを定義したとき、 <- この時のアスタリスクと
pは自身が差しているアドレスを、
*pは自身が差しているアドレスにある変数の値を表す <- この時のアスタリスクは意味が違う
って事になる。
前者のアスタリスクは宣言時に「ポインタ型」を生成する為の機構、後者のアスタリスクを間接参照演算子っつーんだ。
Cの極悪仕様は、全く違う機能に同じ記号(アスタリスク)を採用している事となり、混乱の元となっている。
つまり、貴方のように(貴方が悪いわけじゃない)、
*pは自身が差しているアドレスにある変数の値を表す
って捉えてると、宣言時の
int型のポインタ*pを定義
の意味が分からなくなるんだ。分からなくなって当然、と言う。
厳密に言うと、この最初の宣言は
int型のポインタ変数pをアスタリスクを用いて定義
と言う意味なんだ。
なお、本当の事を言うと、僕はCは全然得意じゃないんだけど、細かい事を言う人は、与題のコードを次のように書くと思う。
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <inttypes.h> 4 5int main(void) 6{ 7 int16_t a = 10; 8 int16_t *p = &a; 9 int16_t *k = NULL; /* ポインタ変数宣言時は必ずNULLで初期化する */ 10 k = &a; 11 printf("%" PRId16 " %" PRId16 "\n", *p, *k); /* 10 10 */ 12 return EXIT_SUCCESS; 13}
多分、「ポインタ変数だけ」で宣言しない。必ずNULL
で初期化する。
基本的にCはアセンブリ言語に毛が生えた程度の言語なんで、ポインタ変数の初期値が「何でも良い」的なコーディングは、バグの元なんで避けるんじゃないだろうか。
結果、Cプログラマはこっちの記述形式
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <inttypes.h> 4 5int main(void) 6{ 7 int16_t a = 10; 8 int16_t *p = &a; 9 printf("%" PRId16 "\n", *p); 10 return EXIT_SUCCESS; 11}
を好むんじゃなかろうか。一々宣言時にNULL
で初期化しといて、改めて代入、ってのは面倒くさいからね。
ただ、貴方のように初心者で、「書き方を学んでいる」最中なら、どうしてもポインタ変数「だけ」を宣言する場合、NULL
で初期化するクセは付けておいた方がいいと思う。
その方が意味は分かりやすくなるしね。
あと、Cが得意で大好きな人とか、あるいは教科書だと圧倒的に与題のような「ポインタ変数宣言」の書き方をするんだけど、僕のようにCがあまり得意じゃない人間とか、あるいはC初心者には次のような書き方の方が向いてると思う。
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <inttypes.h> 4 5int main(void) 6{ 7 int16_t a = 10; 8 int16_t* p = &a; /* アスタリスクはintの方に付ける */ 9 printf("%" PRId16 "\n", *p); 10 return EXIT_SUCCESS; 11}
宣言時のデータ型の方にアスタリスクを付ける。「機能が違うんだから書き方も違うべき」って事だな。
もっとも、これもC言語のクソ仕様で(笑)、int *p;
と言う宣言とint* p;
と言う宣言の意味が全く同じ、ってのはヘンなんだけどね(笑)。
なお、本当はこういう説明はご法度なんだけど、敢えて「教育用言語」として設計されたPascalを使うと、与題のようなコードはこう書く。
Pascalではポインタ絡みはキャレットを使う。
Pascal
1program foo(input, output); 2var 3 a : integer; 4 k : ^integer; {宣言時は前方から} 5 begin 6 a := 10; 7 new(k); 8 k^ := a; {使用時は後方から} 9 writeln(k^); 10 end. 11
Pascalはさすがに教育用、として開発された背景があるんで、この辺の「記法」はCに比べると紛れがない、です。
なお、フリーで使えるコンパイラとしてはFree Pascalが有名です。
(もっとも本当は、Pascal方言のDelphiのクローンが主眼ですが)
あと、Cのポインタに纏わる話だと、Web上だとこのページが詳しいかな。
以上。
投稿2025/05/22 15:39
編集2025/05/23 07:16総合スコア17
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/05/22 23:33
2025/05/23 00:11 編集

0
int *p
という宣言は、int型のポインタを宣言する以上の意味はありません
int a
に置き換えた場合、文脈的には以下と等価です
int a; a=10;
*p
というのはあくまでintを返す書式の一つに過ぎません
例えば次のように書けます
int a,*p; a=10; p=&a;
これはa
と書くか*p
と書くとint
を返すというルールを定めていると読めます
この内p
はポインタとなるので、int
を返すためにまずアドレスを取得する必要が生じます
ポインタ変数というのはあくまでアドレスを値として格納する変数であって、p
やk
自身がa
のアドレスの格納場所を示すシンボル、もとい識別子となっています
これはa
が10
を格納するデータ領域のシンボルとなっていることと同義です
*p
はp
自身のアドレスを指しているわけではありません
p
がa
のアドレスを指しており、int *p
はただの変数宣言です
型部分を()
で括るなら
(int) a; (int *)p;
となります
投稿2025/05/22 12:47
総合スコア125
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。