見出し画像

カーニハンCを読む 2.12 優先度と評価順序

カーニハンCというのは、次の書籍のことである。

プログラミング言語C
B.W.カーニハン、D.M.リッチー著 石田晴久訳

先日書いた記事で演算子の優先順位等々についてなんだかモヤモヤしたので、元祖を引っ張り出してきた次第である。カーニハンCは、私がC言語を始めた頃はバイブルと言われた。C言語に関する本は数あれど、本書が元祖だと言って差し支えない(と思う)。

--------------------------------------------------
[2024/5/11 追記]
この記事は間違っていて、正しくはこちら。

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


「2.12 優先度と評価順序」

そのカーニハンCに、まさにピッタリな章がある。
それがこれである。

2.12 優先度と評価順序

そして、いきなり優先順位と結合規則をまとめた表がでてくる。そこでは、++、-- 演算子の優先度は次のようになっている。

『プログラミング言語C』2.12 優先度と評価順序

間接参照の* と同じ優先度である。
後置と前置にも差違はない。
これは第二版のANSI準拠版も変わらない。

++(後置)、--(後置)の優先度が上がったのはいつからなのだろうか。なんとなくであるが、「->」と「++」の関係性が理由のような気がする。Microsoft仕様という可能性もあるが。

ちなみに、「--」の横に記載されている「-」は、
二項演算子の「-」(z = x - y)ではなくて、
単項演算子の「-」(x = -y)である。

単項演算子「+」が抜けているが、これは第二版で追加されている。


多くの言語と同様に、Cでは演算子の被演算数の評価順序は指定しない

この章を読み進めると次のような文章に行き当たる。

多くの言語と同様に、Cでは演算子の被演算数の評価順序は指定しない

え?
被演算数の評価順序は指定しない?
なら、さっきの表はなんなんだと思えば、

「優先度と結合規則をまとめたもの」

である。結局のところ、この表の優先度は
評価順序に対する優先度ではなくて、
結合規則に対する優先度である(多分)。

章のタイトルが「優先度と評価順序」なので、なんだかややこしい。タイトルからは評価順序の優先度を規定しているととらえかねない。しかし、実際は違う。

結合規則の優先度を規定したのであって、
評価順序の優先度を規定したわけではない
(多分)

結合規則に優先度があっても
評価順序に優先度がないのなら、
章を分けて書いてくれた方がわかりやすいような気がする。これでは甚だ誤解を招きそうである。特に「優先度」と言われれば、ほぼ評価順序ととらえることの方が多くはないか。カーニハンCには、C言語の仕様規定というよりもC言語の指南書という赴きがあって、これはこれでいいところもあるんだが、いささか系統的な表現に欠けるように思える。要するに思い付くままに書き連ねただけという感じなんである。

但し、評価順序が規定されないということについてはさらに紙面を費やして言及している。特に関数の呼び出しと演算子の関係については。

x = f() + g();
これは、f()g() のどちらが先に評価されるかは規定されていない

printf("%d %d\n", ++n, power(2, n));
これも、 ++n と power(2, n) のどちらが先に評価されるかは規定されていない。関数の引数が評価される順序が規定されていないからである。


「a[i] = i++」の評価順序は規定されない

「2.12 優先度と評価順序」では、さらに続けてこうある。少し長いが引用する。

 関数呼出し,入れ子になった代入文,インクレメントとデクレメントの演算子は“副作用”を引き起こす。つまり,ある変数が式の評価の副産物として変化するのである。副作用を引き起こす式は,変数が格納される順序に微妙に関係する。その典型的な例が次の文である。
 a[i] = i++;
問題点は,添字 i の値が古いのか新しいのかである。コンパイラはこれを異なる形で実行しうるから,その解釈によって異なる結果になる。いつ副作用(実際の変数への代入)が発生するかは,コンパイラの判断任せである。なぜなら,最適な実行順序はマシン・アーキテクチャに強く依存するからである。

「インクレメント」は転記ミスではなく、そう書いてある。時代の違いなんだろう。最近は「インクリメント」という方が多いかと思う。

さてこの引用文であるが、次のコードの評価順序は規定されないというのである。

a[i] = i++;

右辺の「i++」の評価順序は確定している。
「i」が評価されてから「++」される。
それは「++(後置)」の規則である。
問題は「a[i]」である。ここの「i」が、

インクリメントする前なのか
インクリメントした後なのか

実は規定されていない。
直感的には「インクリメントする前に決まってるやん」という気もするが、アセンブラに展開することを想像してみるとはたと悩む。後置の評価順序に関する規定はあくまで「i++」だけにかかるようである。式の他の部位には関係しないということだ。それが例え同じ変数であったとしても、である。


「while (*s++ = *t++)」の評価順序に問題はない

一方で、カーニハンCの「5.5 文字ポインタと関数」には次のようなコードもある。

5.5 文字ポインタと関数
while (*s++ = *t++)

所謂「*p++」形式である。関数「strcpy」(文字列をコピーする関数)として記載されているコードだ。なんというか、慣れないとわかりにくい。実際、カーニハンCでも次のように紹介されている。

これは一見してわかりにくいように見えるが,この記法はかなり便利なものであり,Cプログラムでよく見かけるという理由から,こうした慣用法はマスターすべきである。

『Cプログラムでよく見かける』ということが理由になるのかという気もするが、少なくとも「よく見かける」=「コンパイル依存しにくい」ということは言えるかもしれない。要するに決まりきった書き方だから覚えろということであるらしい。

参考までに次のように解釈される。

*t++ の値は,t がインクレメントされる前に指していた文字である。後置演算子++はこの文字が使われてしまうまで t を変更しない。同様にして,文字は s がインクレメントされる前に s の指していた場所に格納される。

『プログラミング言語C』

ちなみに、while文の中身が「==」でなく「=」になっているのは誤記ではない。「!= '\0'」は実は冗長なのだそうだ。そう言ってわざわざ「!= '\0'」を省いたコードなんである。昨今の諸々のコーディング規約に違反しそうではあるが。


優先順位と評価順序については以上である。
久しぶりにカーニハンCを読んでみるのも、なかなか面白い。単に言語仕様を説明するだけでなく、いろいろに指南していたりする。「~すべき」や、あるいはまた「~すべきでない」という表現は、最近の言語解説書ではあまり見ないかもしれない。

一方で、古いルールもある。C言語はかねてより自由度が高いと言われているが、それがまた不具合につながりやすいという側面を持つ。プログラマーの人口が増えてくると、この「不具合につながりやすい」ということが重くなってくる。このため、昨今では様々に規約やルールも増え、またC言語そのものもアップデートされてきた。古い仕様がまた、少し懐かしかったりする。

関数や引数の無指定の型は int であり

『プログラミング言語C』

などとあって、びっくりしたりするんである(笑)。いやいや、今では無指定を許容するCコンパイラなどないだろう。


この記事が気に入ったらサポートをしてみませんか?