3章. 式
3.1:
なぜ以下のコードはうまく動かないか。
a[i] = i++;
A:
i++は副作用をもたらす。つまりiの値を変更する。式の他のところで
iが参照されているので、このことが未定義の振舞いを引き起こす
(K&Rの言い回しではこの式の振る舞いは不定であると取れるが、C規
格は一段と厳しく未定義であるとしている。-- 質問11.33を参照のこ
と)。
References:
K&R1 Sec. 2.12; K&R2 Sec. 2.12; ANSI Sec. 3.3; ISO
Sec. 6.3.
3.2:
私が使っているコンパイラでは以下のコードで49を出力する。
int i = 7;
printf("%d\n", i++ * i++);
評価の順にかかわりなく、56を出力するのではないのか。
A:
後置増分演算子++も後置減分演算子--も元の値を返した後で、その演
算を行う。この"後"の意味付けがしばしば誤解を招く。前回値を産み
出したすぐ後で、かつ式のその他の部分が評価される前に、++や--が
増分や減分を行うことは保証されていない。単に式が"終了"する前
(ANSI Cの用語でいえば次の"副作用完了点"(sequence point)の前。
質問3.8を参照)のどこかで更新が実行されることが保証されているだ
けである。上の例では、前回値と前回値を掛け合わせてから両方の増
分を実行することをコンパイラは選んだ。
副作用があいまいな部分が複数あるコードのふるまいは、常に未定義
である。(大まかに言うと、「副作用があいまいな部分が複数ある」
とは、++, --, =, +=, -=, etc. の組み合わせのいずれかが1つの式
の中に洗われ、同じオブジェクトが2回変更されたり、変更されてか
ら値を調べられることを意味している。これは荒っぽい定義である。
正確な定義は質問3.8を参照のこと。"未定義"の意味については質問
11.33を参照のこと)自分の使っているコンパイラがそのような副作用
を持つコードをどう実装しているか調べるようなこともしてはいけな
い(多くのCの教科書に載っている愚かな練習問題であるが、お薦めは
しない)。K&Rが指摘しているように「もし様々なマシンでどうやって
やって実行してるのか、わからなければ、わからないということが助
けとなるかもしれない。」
References:
K&R1 Sec. 2.12 p. 50; K&R2 Sec. 2.12 p. 54; ANSI
Sec. 3.3; ISO Sec. 6.3; CT&P Sec. 3.7 p. 47; PCS Sec. 9.5 pp.
120-1.
3.3:
以下のコードをいくつかのコンパイラでコンパイルして実験してみた。
int i = 3;
i = i++;
iの値として、3を与えるもの4を与えるもの7を与えるものがあった。
上のコードの振る舞いは未定義であるのは知っている。しかしどうし
て7になるようなことがあるのか。
A:
未定義の振る舞いというのは、どんなことが起っても不思議がないこ
とを意味している。質問3.9と11.33を参照のこと。(i++も++iもi+1と
は違うことに注意すること。iに1加えたかったら、i=i+1かi++か++i
のどれを使ってもいい。ただし組み合わせてはいけない。質問3.12も
参照のこと。)
3.4:
括弧をつけて評価の順をこっちの好きなようにすることができるか。
もしできないとして、優先順位が評価の順を決めるのではないのか。
A:
常に順番を指定できるわけではない。
演算子の優先順位や括弧を付けることは、式の評価に部分的に順序付
けをするだけである。以下の式を考えて見よう。
f() + g() * h()
掛け算は足し算の前に起こるということはわかっているけれど、3つ
の関数のうちどの関数が最初に呼ばれるかはわからない。
式の一部の評価の順序を保証する必要があるなら、明示的に一時変数
を使い複数の文に分ける必要があるかもしれない。
References:
K&R1 Sec. 2.12 p. 49, Sec. A.7 p. 185; K&R2
Sec. 2.12 pp. 52-3, Sec. A.7 p. 200.
3.5:
それでは&&や||や,(コンマ)はどうなるのか。
while((c = getchar()) != EOF && c != '\n')...
のようなコードを見たことがあるが。
A:
これらの演算子は(三項演算子?:も同様だが)特例であって、左から右
へ順に評価することが保証されている(間に入る評価順序点と同じで
ある。質問3.8を参照のこと)。Cに関するどんな本でも、この点につ
いて明白にしてくれるはずである。
References:
K&R1 Sec. 2.6 p. 38, Secs. A7.11-12 pp. 190-1; K&R2
Sec. 2.6 p. 41, Secs. A7.14-15 pp. 207-8; ANSI Sec. 3.3.13,
Sec. 3.3.14, Sec. 3.3.15; ISO Sec. 6.3.13, Sec. 6.3.14,
Sec. 6.3.15; H&S Sec. 7.7 pp. 217-8, Sec. 7.8 pp. 218-20,
Sec. 7.12.1 p. 229; CT&P Sec. 3.7 pp. 46-7.
3.8:
複雑な式をどうやって理解すればいいのか。「副作用完了点
(sequence point)」とはなにか。
A:
副作用完了点とは騒ぎがおさまってすべての副作用が完了したことが
保証されている(時)点である。(つまり式の本当の終わり、
||,&&,?:,","(カンマ演算子)、関数呼び出しの直前) ANSI/ISO C規格
は以下のように述べている、
直前の副作用完了点と次の副作用完了点までの間に、
式の評価によってオブジェクトに格納された値を
変更する回数は高々一度だけでなければならない。
更に、変更前の値は、格納される値を決定するため
だけにアクセスしなければならない。
二番目の文は理解しにくい。オブジェクトに式の中で書き込むなら、
同じ式内でのそのオブジェクトに対する如何なる且つすべてのアクセ
スは書き込むべき値の計算のためでなければならないと言っている。
この規則により合法な式はアクセスが明らかに変更の前に来るものに
実質的に限定される。
以下の質問3.9を参照のこと。
References:
ANSI Sec. 2.1.2.3, Sec. 3.3, Appendix B; ISO
Sec. 5.1.2.3, Sec. 6.3, Annex C; Rationale Sec. 2.1.2.3; H&S
Sec. 7.12.1 pp. 228-9.
3.9:
以下のコードがあったとして、
a[i] = i++;
a[]のどの要素に書きこまれるかはわからないが、iは確実に一つ大き
くなるね。
A:
違う。式やプログラムが未定義になったら、すべての面で未定義とな
る。質問3.2, 3.3, 11.33, 11.35を参照のこと。
3.12:
式の値を使わないとして、変数に1を加えるのにi++と++iのどちらを
使えばよいのか。
A:
この二つは産み出す値が違うだけで、副作用だけを必要とするときは
まったく同じである。
質問3.3も参照のこと。
References:
K&R1 Sec. 2.8 p. 43; K&R2 Sec. 2.8 p. 47; ANSI
Sec. 3.3.2.4, Sec. 3.3.3.1; ISO Sec. 6.3.2.4, Sec. 6.3.3.1; H&S
Sec. 7.4.4 pp. 192-3, Sec. 7.5.8 pp. 199-200.
3.14:
なぜ以下のコードは私が思った通りに動かないのか。
int a = 1000, b = 1000;
long int c = a * b;
A:
Cの整数の拡張規則により、掛け算がintで実行される。結果は左辺の
long intの変数に拡張されて代入される前に桁溢れしたり桁落ちする
かもしれない、longで演算されるよう明示的にキャストを使う。
long int c = (long int)a * b;
(long int)(a * b)では望む結果が得られないことに注意すること。
整数変数同士割算をおこなって結果を浮動小数点型の変数に代入する
ときにも同じような問題が起こる。
References:
K&R1 Sec. 2.7 p. 41; K&R2 Sec. 2.7 p. 44; ANSI
Sec. 3.2.1.5; ISO Sec. 6.2.1.5; H&S Sec. 6.3.4 p. 176; CT&P
Sec. 3.9 pp. 49-50.
3.16:
条件によって、二つの変数のどちらかに代入する複雑な式がある。以
下のようなコードを使ってもよいか。
((condition) ? a : b) = complicated_expression;
A:
使えない。?:演算子は、たいていの演算子と同じように、値を産み出
す。値に代入することはできない。(言い換えると?:は左辺値を産み
出さない)。 本当にやりたいなら、以下のようなやり方を試せばいい。
*((condition) ? &a : &b) = complicated_expression;
けれど、これはお世辞にも美しいとはいえない。
References:
ANSI Sec. 3.3.15 esp. footnote 50; ISO Sec. 6.3.15;
H&S Sec. 7.1 pp. 179-180.