この記事はC3 Advent Calendar 2022 15日目の記事です。
C3に所属する西園寺やきしゃもです。
普段は自作OSをしてる人間です。
ポインタってなんぞ?
C言語のポインタってなんでしたっけ?
簡単に言えば「アドレスを入れる変数」でした。
ポインタの宣言と利用
int a = 2;
int b = 3;
int *p = &a;
p = &b;
printf("%d\n", *p);
*p = a;
printf("%d\n", *p);
とりあえずポインタに関する混乱を生みやすい記述をまとめてみました。
4行目ではポインタpを宣言してaのアドレスを代入しています。
これでpはaを指すポインタになりました。
5行目でポインタpにbのアドレスを代入しています。
これでpはbを指すポインタになりました。
6行目でpの指す先を表示しています。
7行目でpの指す先にaを代入しています。
ポインタの指している先であるbの値が2になりました。
8行目でpの指す先を表示しています。
ここで大事なのは、4行目の * と6,7,8行目の * は全く別の意味を持つ記号ということです。
4行目は「ポインタ宣言子」といいます。
6,7,8行目は「間接演算子」といいます。
それぞれ説明します。
ポインタ宣言子
ポインタ宣言子というのは、この型はポインタだよ~~と主張する記号です。
「型」という言葉が出てきました。
「型」とは「その変数がどのようなデータをどのくらいの大きさで持っているか」を表す情報です。
int i;
unsigned char c;
double d;
上の3つの変数はそれぞれ型が異なりますね。
一個目の変数はきっと符号付き整数を4バイト分だけ表してくれるでしょう。(バイト数は環境によります。)
二個目の変数はたぶん符号なし整数を1バイト分だけ表してくれるでしょう。
三個目の変数はおそらく浮動小数点数を8バイト分だけ表してくれるでしょう。
ここから先の説明するにはちょっと型について理解をしてもらう必要があるのでちょっと回り道します。
型ってなんぞ?
型ってなんぞと言いながら、すでに前のところでもう述べてますね。
「その変数がどのようなデータをどのくらいの大きさで持っているか」を表すのでした。
ということで、ここでは型というものの構造について触れていきます。
突然ですがクイズです。
C言語に型は何個あるでしょうか?
(ここであなたは知っている型を指折り数えるのです...)
(int, char, float, double...)
...
答えは無限個でした〜〜
無駄な苦労でした〜
で、なぜ無限個あると断言できるかと言うと、型はプログラマが勝手に作れるからです。
作るカラクリはさっきもちょっと述べた「型の構造」にあります。
C言語には「基本型」と「派生型」というものがあります。
「基本型」とはさっきも出てきたint, char, float, doubleとかのことです。
あと、これにunsignedやsignedをつけたものも「基本型」になります。
「基本型」はたくさんありますが、有限個しかないです。
では「派生型」とは何でしょうか。
5つしかないので列挙します。
- ポインタ型
- 配列型
- 関数型
- 構造体型
- 共用体型
もしかしたら「共用体」という言葉は聞いたことがないかもしれませんが、知らなくてもここではとりあえず大丈夫です。
C言語の型はこの基本型単体か、基本方と派生型の組み合わせによって構成されています。
例を挙げておきます。
int a;
double *d;
char str[10];
char (*strs)[5];
1行目ではint型のaを宣言してます。
2行目ではdouble型へのポインタ型のdを宣言しています。
3行目ではchar型の配列型(要素数10)のstrを宣言しています。
4行目ではchar型へのポインタ型の配列型(要素数5)のstrsを宣言しています。
このように、基本型単体か、基本型と派生型の組み合わせによってC言語の型は構成されます。
派生の方法には詳しいルールがありますが、ここでは説明を省略します。
話を戻しましょう。
「ポインタ宣言子」とは、ここで説明した派生型である「ポインタ型」を表す記号になります。
間接演算子
間接演算子は演算子の一種です。
演算子とは + とか - とか * とか、何らかの計算をすることを表す記号です。
間接演算子 * はどのような計算をするかというと、「その変数の中身をアドレスとみて、そのアドレスの表す先の変数として扱うようにする」ということをしています。(これは計算なのか...?)簡単にいうと、手繰り寄せをする演算子ということです。
int a = 2;
int *p = &a;
printf("%d", *p);
このプログラムを実行すると、おそらく
2
という出力が得られます。
pに入ってるのはaのアドレスですが、4行目の間接演算子 * によって手繰り寄せをして、aとして扱うことで2という数値を得ています。
ポインタの * ってなんぞ?
もう一度最初に出たコードを見てみましょう。
int a = 2;
int b = 3;
int *p = &a;
p = &b;
printf("%d\n", *p);
*p = a;
printf("%d\n", *p);
4行目も7行目も*pという書き方をしています。しかし、意味が違いますね。
これは * が実質異なる記号だからです。
4行目の * はポインタ宣言子ですからpがポインタであるということを主張しています。
7行目の * は間接演算子ですから、pの指す先として扱います。
4行目ではあくまでint型へのポインタ型を宣言するために * があるのであって、 手繰り寄せはしていません。
だから
int *p = &a;
と書くことができるんですね。
ここだけ * が「間接演算子」ではなく「ポインタ宣言子」になってるせいで混乱が生じていると思うんですよね。
更なる落とし穴
ポインタを複数同時に宣言するとき
int *p, *q;
と書きますね。
int* p, q;
とは書けません。こう書くと、ポインタpとint型の変数qを宣言していることになります。
これが更なる混乱を招いている原因です。
ここでの * はポインタ宣言子でした。つまり、型の名前がint *で、変数の名前がpとqということです。
ですが なぜか型の名前の一部であるはずのポインタ宣言子 * がそれぞれの変数名にかかっています。
これでは、まるでint型の変数*pと*qを宣言しているように見えます。
これはわかりづらいですね...
おわりに
くっそ読みづらい記事になりましたが、これ全部丸々書き直しを3回やったんで許してください...
もしもっと知りたい人はC言語 ポインタ完全制覇を読んでください!
明日は、kizukuくんの「Rustでフロントエンド開発!なんでみんなやらないの?」です!
乞うご期待!!