見出し画像

だからCってやつは *p++

先日書いたC言語の記事で次のようなコードが出てきた。

*p++

こんな書き方ができるのはC言語だけじゃね?
と思うくらいC言語的なコードである。

私はこのコードが非常に苦手で理解に苦しむんだが、さらには環境依存もしそうであって今までは書いたことがない。主に組み込みの開発をやってきて、たまにCPUが変更になるようなこともあり、その時には往々にしてコンパイラも変わるしということで、どんなコンパイラだって解釈が変わらないようなコードを書くということが習慣になってしまっている。組み込みのソフトウェアの寿命は意外に長く、私の手を離れてから別の人がCPUの置き換えをやって、コンパイルエラーだったり
不具合だったりに付き合わされるというようなことに陥らないでもない。そうすると、なんだか気の毒だったり自分の軽率さを恥じたりということになりかねない。組み込みの場合はあまり処理速度を気にしないということも手伝っているかもしれない。というような私の経験はどうでもいいんだが(ただの言い訳だが)、ちょっと改めてこの「*p++」に向き合ってみようかと思ったのであった。

--------------------------------------------------
[2024/5/11 追記]
以下、ウダウダ書いてるんだがほぼ間違っていて、正しくはこちら。

--------------------------------------------------



1文に演算子がいっぱいある

C言語は、文を「;」で区切る。
であるからして

c = *p++;

というようなコードには次の3つの演算子がある。

=
*
++

さて。
いったいどれから実行されるのか。


だから優先順位がある

まず、演算子には優先順位がある。
1文に複数の演算子がある場合、その優先順位に従って評価される。で、改めて調べてみたんだが、今回の3つの演算子の優先順位を調べると次のようになった。

++(後置)
*
=

え?
「後置の++」は「間接参照の*」より優先順位が高いってか?
そうだっけ?
参考にしたのは次のページである。

Microsoftが完璧かどうかはわからないんだが、私自身も最近のANSI Cをきっちり把握しているわけでもなし、こうなっているのかもしれない。ただ1つ注意しなければならないのは、C言語のコンパイラはいろいろあるが、そのどれもが最新のC言語規約に準じているとは限らないということである。C言語の規約そのものもわりと頻繁に変わるのでなかなか追い付かない、ということもある。

それはともかくも、「優先順位が同じなら…云々」という話をしようとしていたんだが、優先順位が同じでないならそういう話につながらず、従って出鼻を挫かれた感じである。ともかくもさきに進めよう。


++(後置)の優先順位が高いんですけど

++(後置)の優先順位が高いということであるが、すると次の式はどう処理されるのか。

c = *p++;

次のようになるのか?

p++
*p
c =
 

いや、もう、ここからアヤシイ。
優先順位的にはそうなるんだけど、「++(後置)」は他の演算子が処理された後に処理される。「++」を取っ払った全てを処理してから、しかる後に「++」されるんである。だから、こうなる。

*p
c =
 
p++

*p をとってきて、それを c に代入して、その後に p++ するんである。


ならば、++ の最強優先順位って何?

先ほどのリンク先によると、++(後置)の優先順位は最強レベルである。なのに、なんでこの順で処理されるん?

*p
c =
 
p++

C言語の言語仕様で言われるところの優先順位というのは、処理順序の優先順位ではないんである(多分)。ではこの優先順位とは何か。処理の結び付きに対する優先順位なんである(多分)。今回の場合、「p」に対して2つの演算子がくっついている。「*」と「++」である。

(1)優先順位が ++ → * の順序だと
先に ++ がひっついて p++

(2)優先順位が * → ++ の順序だと
先に * がひっついて(*p)++

となる。

それって、結局のところ

優先順位=処理の順番といっしょじゃね?

という気もするんだがそれが違うんである。

p にひっついてる演算子は2つ 「*」と「++」
p にどちらが先にひっつくのかは優先順位で決まる
だが、どちらの演算子が先に処理されるのかは優先順位の順とは限らない

* より先に ++ とひっついたとして、 *p と p++ のどちらを先に処理するのかで結果が変わる。

char a[4] = {1, 2, 3, 4};
char* p = a;
char c = *p++;

上記のようなコードでは、

p++ → *p の順で処理されると c = 2
*p → p++ の順で処理されると c = 1

となる。

*p++ には演算子が2つあって
p と結び付くのが * と ++ のどちらが先かという問題と
 * と ++ のどちらが先に演算されるのかという問題の
2種類の問題がある

ということなんである。
*p++ の場合、* より ++ の方が優先順位が高いので p に先に付く演算子は「++」であるが、「++」は後置なので(++ が後ろに書かれているので) * より後に ++ が演算される。
だから、

*p が評価されてから
p++ が評価される

という動きになる。


優先順位をはっきりさせるために ( ) をつけてはどうかと思うが

このC言語の演算子の優先順位であるが、この優先順位をきっちり記憶してコードを書くのはなかなか至難の技である。かと言って、毎回言語仕様を繙くのも煩わしい。そのためによく ( ) で括れと言われる。

例えば、

if (5 <= a && a <= 10)

というような式の場合、a の両端に <= と && の2つの演算子があってどちらが先に a と結び付くのかという問題がある。&& の優先順位は低いので <= から先に結び付くんであるが、次のように書けばより間違いも起こりにくいだろうという考え方である。

if ((5 <= a) && (a <= 10))

c = *p++ の場合、()を付けるとこうなる。

c = *(p++)

うーん。
p++ の優先順位が高いので、あってるんだが。
あってはいるんだが。
なんだか、いよいよ p++ が「先に」処理されそうに見えてしまう。
だが、 p++ はあくまで後置なので処理されるのは他の演算子が評価された後である。でも、なんだかそうは見えないんだよなぁ。この式が次の順番で処理されるなんて、なんだか呪文のようだよなぁ。

c = *p
p++


じゃあ、p++ を先に処理したい場合はどうやって書けばええのん?

c = *p++

と書いても

c = *(p++)


と書いても

c = *p
p++

という順番で処理されるんだったら、

p++
c = *p

の順番で処理したいときは、いったいどう書けばいいんだろう。
こう書くんである。

c = *++p

え。
ええっ!?

なんか、演算子が2つならんでるんですけど。
「++(前置)」は後置の逆で、他の演算子に先だって処理されるので、この式は次の順番で処理される。

p++
c = *p

うーん。
c = *++p」な風に書くことって、あるんかなぁ。


「++」をいろんなところに移動してみる

c = *p++;

これは既に書いた通り、次のように処理される。

c = *p;
p++;

c = *++p;

それも既に書いた。次のように処理される。

++p;
c = *p;

c = ++*p;

これはこうなる。

++(*p);
c = *p;

前置なのでね、まず「++」が処理される。
それはいいんだが、いったい何を「++」するんだか。
「++(前置)」と「間接参照の*」は優先順位が同じである。この優先順位は「++(後置)」と違うところである。では優先順位が同じだとどうなる? 右からひっつける。

++*p

を右からひっつけると先にひっつくのは「*p」になる。
その後に「++」がひっついて、結果、「++(*p)」になる。でもって、前置なのでまず「++」を処理してから「++」をとっぱらった部分を処理する。結果、こうなる。

++(*p);
c = *p;


以上

ふぅ~。
ちかれたびー。

とりあえず、わかったような気がする。
「++」は一旦取り除いて考える。
その後に、
前置であれば前に、
後置であれば後ろに
「++」の処理をつなげればよろしい。

わかったのなら、これから「*p++」を使うかというと、多分使わない。処理速度やコンパクト化のメリットはあるのかもしれないが、組み込みではリスクの方が大きいように感じる。最近は ROM も RAM も潤沢で、CPUもクロックも速い。コードを読んでいて「?」となるデメリットの方が大きい。私が未来永劫メンテナンスするわけでもないし。

p が構造体だと、どうなるんだろうねぇ。


いいなと思ったら応援しよう!