ポインタ式の記述について考える

C言語。構文が簡素で、記述しやすい言語ですね。よく学習用に用いられています。
とはいえ、不可解な表記がないわけではありません。特に、私が疑問に思ったのは、間接参照演算子「*」です。

間接参照演算子

「*」は、間接参照演算子、といいます。乗算も同じ記号を用いますが、乗算は二項を演算するのに対し、こちらは単項です。両側に作用するか、片側に作用するかの違いですね。
めんどくさいので、この記事では「*」を「ポインタ(演算子)」と表記します。

不可解なポインタ演算記法

さて、ポインタ演算子をどのように記述するよう教わったでしょうか。私は以下のように教わりました。

int i;
int *p;

はい。ざっくりポイントをまとめます。

  • ポインタは変数の前につける。
  • pの型は「int *」型(実際にそう呼ぶかどうかではなく「ポインタ」型であると言いたいのです)。

こうでした。

私はこのとき、大きな矛盾を感じたのです。

  • 型と変数名をきっちり分けて書くのが、C言語として綺麗な書き方ではないのか。
  • ポインタのキャストにも同様にするが、汚いのではないか。
    • 仮に、キャストだけスペースしなかったら、統一的な書き方ではなくなる。
    • 逆に、キャストのスペースは汚い。

いろいろありますが、主にこの辺。ポインタ変数を複数定義するときに、それぞれに「*」をつけなければいけない実装も文句ありました。

気持ち悪さを少しだけ解消する

今日突然、思い出したように気がついたのです。何故このように書くのかを。
さて、以下は私の主なスペーシングです。それほど不可解ではないと思います。

for (i = 0; i < n; i++) {
    s += p[i]->n / 2;
}

ここで、突然ですが、演算子の結合規則を考えます。

  • 後置増分は左結合です。
  • 代入文は右結合です。
  • 算術式は左結合です。
  • 添え字は左結合です。
  • 比較は左結合です。
  • アローは左結合です。

では、これらを無駄に強調したスペーシングで書いてみます。

for ( i =0 ; i< n; i++ ) {
    s +=(p[i])-> n/ 2;
}

大分変わりましたね。スペースって大事ですね。
ところで、代入文や算術式、比較式は、Cでは両辺を対等に考えるのが普通ですので、そのあたりのスペースを両辺対等におきます。更に、アロー演算子は間接参照と構造体メンバ変数参照を兼ねたような役割であり、構造体参照のポインタ版のように使うのでしたら、(変数名->メンバ名)は一括りとして扱いますので、両辺スペースを消します。
するとどうでしょうか。ほとんど元に戻るのではないでしょうか。

ポインタに関して

ポインタは単項演算子です。右辺がー左辺がーとか、ありません。
では、素直に結合規則を強調した書き方をすると、やはり冒頭で紹介した形になるわけです。
そこで、変数定義の見方を変えます。

int n, *p, **pp, *q;

これ、やっぱりみんなに「*」をつけるのは、今までの解説だとちょっと不可解です。
そこで、こう見ます。

  • 一文は、int型の定義。
  • 「n」は、int型の変数。
  • 「*p」は、int型の変数。
    • よって、「p」はint型へのポインタ。
  • 「*pp」は、int型の変数。
    • よって、「*pp」は、int型へのポインタ。
      • よって、「pp」は、int型へのポインタのポインタ。
  • 「*q」は、int型の変数。
    • よって、「q」はint型へのポインタ。

トップレベルの項目だけを眺めてみてください。こうすれば不思議さが減少するのではないでしょうか。
最初からこのように教えろ、と思わなくもないのですが、やはり無理でしょう。
だってこれ、すごく回りくどいんですもの。ポインタ型(派生的な型)で、普通困らないじゃないですか。だから、仕組みを理解したあとに説明されてもいいんじゃないですかね。
少なくとも私は、説明が欲しかったですね。

キャスト(保留)

キャスト演算、あれはどうしましょうか。
括弧の位置的には、前述の見方はできませんね。